@meshmakers/octo-ui 3.3.970 → 3.3.1000

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,32 +1,32 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Component, Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, ElementRef, forwardRef, ViewChild, signal, computed, Directive, input, output, effect, InjectionToken, HostListener, makeEnvironmentProviders } from '@angular/core';
2
+ import { inject, Component, Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, ElementRef, forwardRef, ViewChild, signal, computed, input, model, output, effect, untracked, Directive, InjectionToken, HostListener, makeEnvironmentProviders } from '@angular/core';
3
3
  import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
4
- import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeSelectorService, CkTypeAttributeService, GetEntitiesByCkTypeDtoGQL, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, GetCkTypesDtoGQL, GetCkModelByIdDtoGQL, LevelMetaData, RtAssociationMetaData, CkTypeMetaData, GraphDirectionDto, AssociationModOptionsDto, GraphQL, DeleteStrategiesDto, provideOctoServices } from '@meshmakers/octo-services';
4
+ import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeSelectorService, CkTypeAttributeService, GetEntitiesByCkTypeDtoGQL, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, GetCkTypesDtoGQL, GetCkModelByIdDtoGQL, LevelMetaData, RtAssociationMetaData, CkTypeMetaData, GraphDirectionDto, AssociationModOptionsDto, GraphQL, DeleteStrategiesDto, CommunicationService, provideOctoServices } from '@meshmakers/octo-services';
5
5
  import { Roles, provideMmSharedAuth } from '@meshmakers/shared-auth';
6
- import { WindowStateService, EntitySelectInputComponent, DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, TreeComponent, ListViewComponent, FetchResultTyped, DataSourceBase, InputService, BaseTreeDetailComponent, provideMmSharedUi } from '@meshmakers/shared-ui';
6
+ import { WindowStateService, EntitySelectInputComponent, DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, TreeComponent, ListViewComponent, FetchResultTyped, DataSourceBase, InputService, BaseTreeDetailComponent, ConfirmationService, provideMmSharedUi } from '@meshmakers/shared-ui';
7
7
  import * as i1$2 from '@angular/common';
8
8
  import { CommonModule, Location } from '@angular/common';
9
9
  import * as i1 from '@angular/forms';
10
10
  import { FormsModule, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormArray, Validators, FormGroup, FormBuilder } from '@angular/forms';
11
11
  import * as i1$1 from '@progress/kendo-angular-buttons';
12
12
  import { ButtonsModule, ButtonModule, KENDO_BUTTONS, ButtonComponent } from '@progress/kendo-angular-buttons';
13
- import { WindowRef, WindowModule, WindowService, WindowCloseResult, DialogRef, DialogService, DialogCloseResult } from '@progress/kendo-angular-dialog';
14
- import * as i4 from '@progress/kendo-angular-dropdowns';
15
- import { DropDownListModule, DropDownsModule, AutoCompleteModule, KENDO_DROPDOWNS } from '@progress/kendo-angular-dropdowns';
13
+ import { WindowRef, WindowModule, WindowService, WindowCloseResult } from '@progress/kendo-angular-dialog';
14
+ import * as i3$1 from '@progress/kendo-angular-dropdowns';
15
+ import { DropDownListModule, DropDownsModule, AutoCompleteModule, ComboBoxModule, KENDO_DROPDOWNS } from '@progress/kendo-angular-dropdowns';
16
16
  import * as i3 from '@progress/kendo-angular-grid';
17
17
  import { GridModule } from '@progress/kendo-angular-grid';
18
18
  import * as i5$1 from '@progress/kendo-angular-icons';
19
19
  import { IconsModule, SVGIconModule, SVGIconComponent } from '@progress/kendo-angular-icons';
20
20
  import * as i5 from '@progress/kendo-angular-inputs';
21
21
  import { InputsModule, TextBoxModule, KENDO_INPUTS, SwitchModule } from '@progress/kendo-angular-inputs';
22
- import { searchIcon, sortAscSmallIcon, sortDescSmallIcon, filterClearIcon, fileIcon, folderIcon, calendarIcon, checkboxCheckedIcon, listUnorderedIcon, chevronRightIcon, chevronDownIcon, downloadIcon, windowIcon, arrowRightIcon, arrowLeftIcon, chevronDoubleRightIcon, chevronDoubleLeftIcon, arrowUpIcon, arrowDownIcon, pencilIcon, trashIcon, copyIcon, folderMoreIcon, folderOpenIcon, gearIcon, plusIcon, minusIcon, dollarIcon, hyperlinkOpenIcon, infoCircleIcon, eyeIcon, arrowRotateCcwIcon, locationsIcon, arrowRotateCwIcon, xIcon, checkCircleIcon, exclamationCircleIcon, xCircleIcon } from '@progress/kendo-svg-icons';
23
- import { Subject, firstValueFrom, Subscription, of, forkJoin, takeUntil as takeUntil$1, catchError as catchError$1, map as map$1, defer, from, finalize, switchMap as switchMap$1, startWith } from 'rxjs';
22
+ import { searchIcon, sortAscSmallIcon, sortDescSmallIcon, filterClearIcon, fileIcon, folderIcon, calendarIcon, checkboxCheckedIcon, listUnorderedIcon, chevronRightIcon, chevronDownIcon, downloadIcon, windowIcon, arrowRightIcon, arrowLeftIcon, chevronDoubleRightIcon, chevronDoubleLeftIcon, arrowUpIcon, arrowDownIcon, pencilIcon, trashIcon, copyIcon, folderMoreIcon, folderOpenIcon, gearIcon, plusIcon, minusIcon, dollarIcon, hyperlinkOpenIcon, infoCircleIcon, eyeIcon, arrowRotateCcwIcon, locationsIcon, arrowRotateCwIcon, xIcon, checkCircleIcon, exclamationCircleIcon, xCircleIcon, warningCircleIcon, gridIcon, linkIcon } from '@progress/kendo-svg-icons';
23
+ import { Subject, firstValueFrom, Subscription, of, forkJoin, map as map$1, takeUntil as takeUntil$1, catchError as catchError$1, defer, from, finalize, switchMap as switchMap$1, startWith } from 'rxjs';
24
24
  import { debounceTime, distinctUntilChanged, map, switchMap, tap, catchError, takeUntil } from 'rxjs/operators';
25
25
  import { LoaderModule, BadgeModule } from '@progress/kendo-angular-indicators';
26
- import { isCompositeFilterDescriptor } from '@progress/kendo-data-query';
27
- import { TreeItemDataTyped } from '@meshmakers/shared-services';
28
26
  import * as i1$3 from 'apollo-angular';
29
27
  import { gql } from 'apollo-angular';
28
+ import { isCompositeFilterDescriptor } from '@progress/kendo-data-query';
29
+ import { TreeItemDataTyped } from '@meshmakers/shared-services';
30
30
  import * as i7 from '@progress/kendo-angular-dateinputs';
31
31
  import { DateInputsModule, KENDO_DATEINPUTS } from '@progress/kendo-angular-dateinputs';
32
32
  import { IntlModule } from '@progress/kendo-angular-intl';
@@ -34,7 +34,7 @@ import { PopupModule, PopupComponent } from '@progress/kendo-angular-popup';
34
34
  import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
35
35
  import { NotificationService } from '@progress/kendo-angular-notification';
36
36
  import * as i1$4 from '@progress/kendo-angular-layout';
37
- import { TabStripModule, CardModule, KENDO_LAYOUT } from '@progress/kendo-angular-layout';
37
+ import { TabStripModule, CardModule, KENDO_LAYOUT, LayoutModule } from '@progress/kendo-angular-layout';
38
38
  import { rxResource, toSignal, toObservable } from '@angular/core/rxjs-interop';
39
39
  import * as i1$5 from '@progress/kendo-angular-label';
40
40
  import { KENDO_LABEL } from '@progress/kendo-angular-label';
@@ -419,7 +419,7 @@ class AttributeSortSelectorDialogComponent {
419
419
  <button kendoButton themeColor="primary" (click)="onOk()">Apply</button>
420
420
  </div>
421
421
  </div>
422
- `, isInline: true, styles: [":host{display:block;height:100%}.attribute-sort-selector-container{display:flex;flex-direction:column;height:100%;padding:16px 20px;box-sizing:border-box;gap:16px}.filter-container{display:flex;gap:12px;flex-shrink:0}.search-input{flex:1}.type-filter-dropdown{width:160px;flex-shrink:0}.lists-container{display:flex;gap:16px;flex:1;min-height:0}.list-section{flex:1;display:flex;flex-direction:column;min-height:0}.list-section h4,.sort-options-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600;flex-shrink:0}.attribute-grid{border-radius:4px;flex:1;min-height:200px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.sort-options-section{width:120px;flex-shrink:0;display:flex;flex-direction:column;padding-top:32px}.sort-buttons{display:flex;flex-direction:row;gap:4px}.sort-button{flex:1;min-width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center}.add-button{width:100%;margin-top:16px}.sort-display{display:flex;align-items:center;gap:6px}.sort-indicator{font-size:14px;font-weight:700;color:var(--kendo-color-primary, #ff6358)}.action-bar{display:flex;justify-content:flex-end;gap:8px;flex-shrink:0;padding-top:8px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: WindowModule }] });
422
+ `, isInline: true, styles: [":host{display:block;height:100%}.attribute-sort-selector-container{display:flex;flex-direction:column;height:100%;padding:16px 20px;box-sizing:border-box;gap:16px}.filter-container{display:flex;gap:12px;flex-shrink:0}.search-input{flex:1}.type-filter-dropdown{width:160px;flex-shrink:0}.lists-container{display:flex;gap:16px;flex:1;min-height:0}.list-section{flex:1;display:flex;flex-direction:column;min-height:0}.list-section h4,.sort-options-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600;flex-shrink:0}.attribute-grid{border-radius:4px;flex:1;min-height:200px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.sort-options-section{width:120px;flex-shrink:0;display:flex;flex-direction:column;padding-top:32px}.sort-buttons{display:flex;flex-direction:row;gap:4px}.sort-button{flex:1;min-width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center}.add-button{width:100%;margin-top:16px}.sort-display{display:flex;align-items:center;gap:6px}.sort-indicator{font-size:14px;font-weight:700;color:var(--kendo-color-primary, #ff6358)}.action-bar{display:flex;justify-content:flex-end;gap:8px;flex-shrink:0;padding-top:8px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i3$1.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: WindowModule }] });
423
423
  }
424
424
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AttributeSortSelectorDialogComponent, decorators: [{
425
425
  type: Component,
@@ -938,7 +938,7 @@ class CkTypeSelectorDialogComponent {
938
938
  <button kendoButton themeColor="primary" [disabled]="!selectedType || (selectedType.isAbstract && !allowAbstract)" (click)="onConfirm()">OK</button>
939
939
  </div>
940
940
  </div>
941
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%}.ck-type-selector-container{display:flex;flex-direction:column;flex:1;min-height:0;padding:20px;box-sizing:border-box;gap:12px}.filter-container{margin-bottom:16px;flex-shrink:0}.filter-row{display:flex;gap:16px;align-items:flex-end}.filter-item{display:flex;flex-direction:column;gap:4px}.filter-item label{font-size:12px;font-weight:500}.filter-item.flex-grow{flex:1}.filter-item.filter-actions{flex-shrink:0}.filter-input{min-width:180px}.grid-container{flex:1;min-height:0;display:flex;flex-direction:column}.grid-container kendo-grid,.grid-container .type-grid{flex:1;min-height:200px}.type-grid{border-radius:4px}.type-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.abstract-type{font-style:italic;opacity:.7}.final-type{font-weight:600}.type-badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:10px;margin-left:8px;text-transform:uppercase}.type-badge.abstract{background-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-on-warning, #000);opacity:.8}.type-badge.final{background-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-on-success, #fff);opacity:.8}.selection-info{margin-top:12px;padding:8px 12px;background:var(--kendo-color-success-subtle, #d4edda);border:1px solid var(--kendo-color-success, #28a745);border-radius:4px;font-size:14px;flex-shrink:0}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding:8px 20px 0;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: WindowModule }] });
941
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%}.ck-type-selector-container{display:flex;flex-direction:column;flex:1;min-height:0;padding:20px;box-sizing:border-box;gap:12px}.filter-container{margin-bottom:16px;flex-shrink:0}.filter-row{display:flex;gap:16px;align-items:flex-end}.filter-item{display:flex;flex-direction:column;gap:4px}.filter-item label{font-size:12px;font-weight:500}.filter-item.flex-grow{flex:1}.filter-item.filter-actions{flex-shrink:0}.filter-input{min-width:180px}.grid-container{flex:1;min-height:0;display:flex;flex-direction:column}.grid-container kendo-grid,.grid-container .type-grid{flex:1;min-height:200px}.type-grid{border-radius:4px}.type-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.abstract-type{font-style:italic;opacity:.7}.final-type{font-weight:600}.type-badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:10px;margin-left:8px;text-transform:uppercase}.type-badge.abstract{background-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-on-warning, #000);opacity:.8}.type-badge.final{background-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-on-success, #fff);opacity:.8}.selection-info{margin-top:12px;padding:8px 12px;background:var(--kendo-color-success-subtle, #d4edda);border:1px solid var(--kendo-color-success, #28a745);border-radius:4px;font-size:14px;flex-shrink:0}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding:8px 20px 0;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i3$1.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: WindowModule }] });
942
942
  }
943
943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CkTypeSelectorDialogComponent, decorators: [{
944
944
  type: Component,
@@ -3393,7 +3393,7 @@ class CkTypeSelectorInputComponent {
3393
3393
  (click)="openDialog()">
3394
3394
  </button>
3395
3395
  </div>
3396
- `, isInline: true, styles: [":host{display:block;width:100%}.ck-type-select-wrapper{position:relative;display:flex;align-items:center;width:100%;gap:4px}.ck-type-select-wrapper.disabled{opacity:.6;pointer-events:none}.ck-type-autocomplete{flex:1;min-width:0}.dialog-button{flex-shrink:0;height:30px;width:30px;padding:0;display:flex;align-items:center;justify-content:center}.ck-type-item{padding:4px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-data-message{padding:8px 12px;color:var(--kendo-color-subtle);font-style:italic;text-align:center}.advanced-search-footer{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;color:var(--kendo-color-primary);border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-surface-alt);transition:background-color .2s}.advanced-search-footer:hover{background:var(--kendo-color-base-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i4.AutoCompleteComponent, selector: "kendo-autocomplete", inputs: ["highlightFirst", "showStickyHeader", "focusableId", "data", "value", "valueField", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "clearButton", "suggest", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoAutoComplete"] }, { kind: "directive", type: i4.FooterTemplateDirective, selector: "[kendoDropDownListFooterTemplate],[kendoComboBoxFooterTemplate],[kendoDropDownTreeFooterTemplate],[kendoMultiColumnComboBoxFooterTemplate],[kendoAutoCompleteFooterTemplate],[kendoMultiSelectFooterTemplate],[kendoMultiSelectTreeFooterTemplate]" }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "directive", type: i4.NoDataTemplateDirective, selector: "[kendoDropDownListNoDataTemplate],[kendoDropDownTreeNoDataTemplate],[kendoComboBoxNoDataTemplate],[kendoMultiColumnComboBoxNoDataTemplate],[kendoAutoCompleteNoDataTemplate],[kendoMultiSelectNoDataTemplate],[kendoMultiSelectTreeNoDataTemplate]" }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: IconsModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: SVGIconModule }] });
3396
+ `, isInline: true, styles: [":host{display:block;width:100%}.ck-type-select-wrapper{position:relative;display:flex;align-items:center;width:100%;gap:4px}.ck-type-select-wrapper.disabled{opacity:.6;pointer-events:none}.ck-type-autocomplete{flex:1;min-width:0}.dialog-button{flex-shrink:0;height:30px;width:30px;padding:0;display:flex;align-items:center;justify-content:center}.ck-type-item{padding:4px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-data-message{padding:8px 12px;color:var(--kendo-color-subtle);font-style:italic;text-align:center}.advanced-search-footer{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;color:var(--kendo-color-primary);border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-surface-alt);transition:background-color .2s}.advanced-search-footer:hover{background:var(--kendo-color-base-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i3$1.AutoCompleteComponent, selector: "kendo-autocomplete", inputs: ["highlightFirst", "showStickyHeader", "focusableId", "data", "value", "valueField", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "clearButton", "suggest", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoAutoComplete"] }, { kind: "directive", type: i3$1.FooterTemplateDirective, selector: "[kendoDropDownListFooterTemplate],[kendoComboBoxFooterTemplate],[kendoDropDownTreeFooterTemplate],[kendoMultiColumnComboBoxFooterTemplate],[kendoAutoCompleteFooterTemplate],[kendoMultiSelectFooterTemplate],[kendoMultiSelectTreeFooterTemplate]" }, { kind: "directive", type: i3$1.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "directive", type: i3$1.NoDataTemplateDirective, selector: "[kendoDropDownListNoDataTemplate],[kendoDropDownTreeNoDataTemplate],[kendoComboBoxNoDataTemplate],[kendoMultiColumnComboBoxNoDataTemplate],[kendoAutoCompleteNoDataTemplate],[kendoMultiSelectNoDataTemplate],[kendoMultiSelectTreeNoDataTemplate]" }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: IconsModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: SVGIconModule }] });
3397
3397
  }
3398
3398
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CkTypeSelectorInputComponent, decorators: [{
3399
3399
  type: Component,
@@ -4059,7 +4059,7 @@ class AttributeSelectorDialogComponent {
4059
4059
  </button>
4060
4060
  </div>
4061
4061
  </div>
4062
- `, isInline: true, styles: [":host{display:block;height:100%}.attribute-selector-container{display:flex;flex-direction:column;height:100%;padding:16px 20px;box-sizing:border-box;gap:16px}.filter-container{display:flex;gap:12px;flex-shrink:0}.search-input{flex:1}.type-filter-dropdown{width:160px;flex-shrink:0}.options-container{display:flex;align-items:center;gap:8px;font-size:.85rem;flex-shrink:0}.option-label{cursor:pointer}.depth-input{width:90px}.lists-container{display:flex;gap:16px;flex:1;min-height:0}.list-section{flex:1;display:flex;flex-direction:column;min-height:0}.list-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600;flex-shrink:0}.attribute-grid{border-radius:4px;flex:1;min-height:200px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.action-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.separator{height:1px;background-color:var(--kendo-color-border, #dee2e6);margin:8px 0}.order-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.order-number{color:var(--kendo-color-primary, #ff6358);font-weight:600;margin-right:8px;min-width:24px;display:inline-block}.single-select-container{flex:1;min-height:0;display:flex;flex-direction:column}.single-select-container .attribute-grid{flex:1}.action-bar{display:flex;justify-content:flex-end;gap:8px;flex-shrink:0;padding-top:8px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "component", type: i5.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i5.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: WindowModule }] });
4062
+ `, isInline: true, styles: [":host{display:block;height:100%}.attribute-selector-container{display:flex;flex-direction:column;height:100%;padding:16px 20px;box-sizing:border-box;gap:16px}.filter-container{display:flex;gap:12px;flex-shrink:0}.search-input{flex:1}.type-filter-dropdown{width:160px;flex-shrink:0}.options-container{display:flex;align-items:center;gap:8px;font-size:.85rem;flex-shrink:0}.option-label{cursor:pointer}.depth-input{width:90px}.lists-container{display:flex;gap:16px;flex:1;min-height:0}.list-section{flex:1;display:flex;flex-direction:column;min-height:0}.list-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600;flex-shrink:0}.attribute-grid{border-radius:4px;flex:1;min-height:200px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.action-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.separator{height:1px;background-color:var(--kendo-color-border, #dee2e6);margin:8px 0}.order-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.order-number{color:var(--kendo-color-primary, #ff6358);font-weight:600;margin-right:8px;min-width:24px;display:inline-block}.single-select-container{flex:1;min-height:0;display:flex;flex-direction:column}.single-select-container .attribute-grid{flex:1}.action-bar{display:flex;justify-content:flex-end;gap:8px;flex-shrink:0;padding-top:8px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i5.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "component", type: i5.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i5.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i3$1.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: WindowModule }] });
4063
4063
  }
4064
4064
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AttributeSelectorDialogComponent, decorators: [{
4065
4065
  type: Component,
@@ -4735,6 +4735,323 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4735
4735
  }]
4736
4736
  }] });
4737
4737
 
4738
+ const GetRuntimeEntityByIdDocumentDto = gql `
4739
+ query getRuntimeEntityById($rtId: OctoObjectId!, $ckTypeId: String!) {
4740
+ runtime {
4741
+ runtimeEntities(ckId: $ckTypeId, rtId: $rtId) {
4742
+ items {
4743
+ rtId
4744
+ ckTypeId
4745
+ rtWellKnownName
4746
+ rtCreationDateTime
4747
+ rtChangedDateTime
4748
+ attributes(resolveEnumValuesToNames: true) {
4749
+ items {
4750
+ attributeName
4751
+ value
4752
+ }
4753
+ }
4754
+ associations {
4755
+ definitions(direction: ANY, first: 0) {
4756
+ totalCount
4757
+ }
4758
+ }
4759
+ }
4760
+ }
4761
+ }
4762
+ }
4763
+ `;
4764
+ class GetRuntimeEntityByIdDtoGQL extends i1$3.Query {
4765
+ document = GetRuntimeEntityByIdDocumentDto;
4766
+ constructor(apollo) {
4767
+ super(apollo);
4768
+ }
4769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
4770
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, providedIn: 'root' });
4771
+ }
4772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, decorators: [{
4773
+ type: Injectable,
4774
+ args: [{
4775
+ providedIn: 'root'
4776
+ }]
4777
+ }], ctorParameters: () => [{ type: i1$3.Apollo }] });
4778
+
4779
+ /**
4780
+ * Pure helpers for extracting runtime data point names from a CK entity. Kept
4781
+ * Angular-free so unit tests can exercise the record-shape branches without
4782
+ * spinning up a TestBed, and so the same logic can be called both from the
4783
+ * service (which fetches the entity via GraphQL) and from any caller that
4784
+ * already has the entity in hand (e.g. the runtime-browser detail pane).
4785
+ *
4786
+ * A "data point" here is one entry of an entity's `States` or `DataPoints`
4787
+ * RecordArray attribute — for Loxone Controls these are the state names
4788
+ * (`tempActual`, `co2`, `humidityActual`, …); for adapters that don't model
4789
+ * sub-states the array is absent and the only available data point is the
4790
+ * default `currentValue` constant the runtime adapter exposes for
4791
+ * single-state polling.
4792
+ */
4793
+ /**
4794
+ * Default data-point name for entities without a States/DataPoints RecordArray.
4795
+ * Matches the constant the runtime adapters (Loxone, MQTT, OPC-UA) use as the
4796
+ * `sourceAttributePath` fallback when no specific state is configured.
4797
+ */
4798
+ const DEFAULT_DATA_POINT = 'currentValue';
4799
+ /**
4800
+ * Extracts the list of available data point names from an entity's attribute
4801
+ * list. Always includes {@link DEFAULT_DATA_POINT} first; named state entries
4802
+ * follow in alphabetical order.
4803
+ *
4804
+ * Tolerates the three record shapes the platform produces for a RecordArray:
4805
+ *
4806
+ * 1. GraphQL: each record is `{ ckRecordId, attributes: [{attributeName, value}, …] }`.
4807
+ * 2. Pipeline / MongoDB: each record is `{ attributes: { Name: "…", ExternalId: "…" } }`.
4808
+ * 3. Flat: each record is `{ Name: "…", ExternalId: "…" }` (older dumps / sample data).
4809
+ *
4810
+ * Returns `[DEFAULT_DATA_POINT]` for null/undefined input, attributes without a
4811
+ * States/DataPoints entry, or malformed RecordArrays.
4812
+ */
4813
+ function extractDataPointNames(attributes) {
4814
+ if (!attributes)
4815
+ return [DEFAULT_DATA_POINT];
4816
+ const statesAttr = attributes.find(a => {
4817
+ const name = a?.attributeName?.toLowerCase();
4818
+ return name === 'states' || name === 'datapoints';
4819
+ });
4820
+ if (!statesAttr?.value)
4821
+ return [DEFAULT_DATA_POINT];
4822
+ const records = coerceToRecordArray(statesAttr.value);
4823
+ if (!records)
4824
+ return [DEFAULT_DATA_POINT];
4825
+ const names = [];
4826
+ for (const record of records) {
4827
+ const name = extractRecordName(record);
4828
+ if (name)
4829
+ names.push(name);
4830
+ }
4831
+ if (names.length === 0)
4832
+ return [DEFAULT_DATA_POINT];
4833
+ return [DEFAULT_DATA_POINT, ...[...names].sort((a, b) => a.localeCompare(b))];
4834
+ }
4835
+ /**
4836
+ * Accepts either an array (GraphQL or in-memory) or a JSON-stringified array
4837
+ * (the wire format some pipelines emit). Returns the parsed array or null
4838
+ * when the value can't be coerced.
4839
+ */
4840
+ function coerceToRecordArray(value) {
4841
+ if (Array.isArray(value))
4842
+ return value;
4843
+ if (typeof value === 'string') {
4844
+ try {
4845
+ const parsed = JSON.parse(value);
4846
+ return Array.isArray(parsed) ? parsed : null;
4847
+ }
4848
+ catch {
4849
+ return null;
4850
+ }
4851
+ }
4852
+ return null;
4853
+ }
4854
+ /** Walks the known record shapes and returns the first non-empty name found. */
4855
+ function extractRecordName(record) {
4856
+ if (!record || typeof record !== 'object')
4857
+ return null;
4858
+ const r = record;
4859
+ // 1. GraphQL shape: attributes = [{attributeName, value}, …]
4860
+ const attrsAny = r['attributes'];
4861
+ if (Array.isArray(attrsAny)) {
4862
+ const nameEntry = attrsAny.find(a => a?.attributeName === 'name' || a?.attributeName === 'Name');
4863
+ const value = nameEntry?.value;
4864
+ if (typeof value === 'string' && value.length > 0)
4865
+ return value;
4866
+ }
4867
+ // 2. Pipeline / MongoDB shape: attributes = { Name, ExternalId, … }
4868
+ if (attrsAny && typeof attrsAny === 'object' && !Array.isArray(attrsAny)) {
4869
+ const attrObj = attrsAny;
4870
+ const value = attrObj['Name'] ?? attrObj['name'] ?? attrObj['stateName'];
4871
+ if (typeof value === 'string' && value.length > 0)
4872
+ return value;
4873
+ }
4874
+ // 3. Flat shape: { Name, ExternalId, … } at the record root
4875
+ const flatName = r['Name'] ?? r['name'];
4876
+ if (typeof flatName === 'string' && flatName.length > 0)
4877
+ return flatName;
4878
+ return null;
4879
+ }
4880
+
4881
+ /**
4882
+ * Resolves the list of runtime data points available on a source entity.
4883
+ * Single source of truth shared by the runtime-browser detail pane (which
4884
+ * already has the entity loaded) and the mapping-edit dialog (which only
4885
+ * has rtId + ckTypeId and has to fetch). Picks the right path automatically:
4886
+ *
4887
+ * - {@link extractFromEntity} is sync — pass a pre-loaded entity.
4888
+ * - {@link load} is async — fetches via `getRuntimeEntityById` then extracts.
4889
+ *
4890
+ * Both paths share the same pure helper so a Loxone Control's state list
4891
+ * (`tempActual`, `co2`, …) is reported identically regardless of who's asking.
4892
+ */
4893
+ class DataPointResolverService {
4894
+ getRuntimeEntityByIdGQL = inject(GetRuntimeEntityByIdDtoGQL);
4895
+ extractFromEntity(entity) {
4896
+ return extractDataPointNames(entity?.attributes?.items);
4897
+ }
4898
+ async load(rtId, ckTypeId) {
4899
+ if (!rtId || !ckTypeId)
4900
+ return [DEFAULT_DATA_POINT];
4901
+ try {
4902
+ const entity = await firstValueFrom(this.getRuntimeEntityByIdGQL
4903
+ .fetch({
4904
+ variables: { rtId, ckTypeId },
4905
+ fetchPolicy: 'network-only',
4906
+ })
4907
+ .pipe(map$1(r => r.data?.runtime?.runtimeEntities?.items?.[0] ?? null)));
4908
+ return this.extractFromEntity(entity);
4909
+ }
4910
+ catch (error) {
4911
+ console.error(`DataPointResolverService.load failed for ${ckTypeId}@${rtId}:`, error);
4912
+ return [DEFAULT_DATA_POINT];
4913
+ }
4914
+ }
4915
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataPointResolverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4916
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataPointResolverService, providedIn: 'root' });
4917
+ }
4918
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataPointResolverService, decorators: [{
4919
+ type: Injectable,
4920
+ args: [{ providedIn: 'root' }]
4921
+ }] });
4922
+
4923
+ /**
4924
+ * Source data-point picker for DataPointMapping `sourceAttributePath`.
4925
+ *
4926
+ * Renders a Kendo combobox of the data points exposed by a source entity —
4927
+ * a Loxone Control's State names (`tempActual`, `co2`, …), an MQTT topic
4928
+ * sub-key, an OPC-UA node attribute, etc. — with `currentValue` as the
4929
+ * always-available default for single-state sources.
4930
+ *
4931
+ * Two ways to feed the picker:
4932
+ * - `entity` — pass a pre-loaded RtEntityDto; the picker reads the
4933
+ * States/DataPoints RecordArray straight off it (no GraphQL roundtrip).
4934
+ * Used by the runtime-browser detail pane.
4935
+ * - `entityRtId` + `entityCkTypeId` — the picker fetches the entity via
4936
+ * `getRuntimeEntityById` and extracts data points itself. Used by the
4937
+ * mapping-edit dialog where only the IDs are known.
4938
+ *
4939
+ * `allowCustom: true` keeps the picker useful when the user needs to escape
4940
+ * the catalogue — e.g. typing a path the entity hasn't published yet, or a
4941
+ * field a future state will expose.
4942
+ */
4943
+ class DataPointPickerComponent {
4944
+ resolver = inject(DataPointResolverService);
4945
+ /** Pre-loaded source entity. When set, no GraphQL call is made. */
4946
+ entity = input(null, ...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
4947
+ /** Source entity rtId. Required (together with ckTypeId) when `entity` is not provided. */
4948
+ entityRtId = input(null, ...(ngDevMode ? [{ debugName: "entityRtId" }] : /* istanbul ignore next */ []));
4949
+ /** Source entity CK type id. */
4950
+ entityCkTypeId = input(null, ...(ngDevMode ? [{ debugName: "entityCkTypeId" }] : /* istanbul ignore next */ []));
4951
+ /** Two-way bound data-point name. */
4952
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4953
+ /** Optional placeholder shown when the combobox is empty. */
4954
+ placeholder = input('e.g. tempActual, currentValue', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
4955
+ /** Disables the combobox without hiding it. */
4956
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4957
+ /** Emits whenever the user enters a custom filter — useful for callers that
4958
+ * want to refine a backing catalogue independently of the value selection. */
4959
+ filterChange = output();
4960
+ options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4961
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
4962
+ /**
4963
+ * Current filter text entered into the combobox. Drives the case-insensitive
4964
+ * *contains* match below. Kendo's combobox by default does not filter the
4965
+ * `[data]` it is given — when `filterable` is true it just emits
4966
+ * `filterChange` events and expects the consumer to refilter. Without this,
4967
+ * typing "co2" would show every data point regardless of name.
4968
+ */
4969
+ filter = signal('', ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
4970
+ filteredOptions = computed(() => {
4971
+ const all = this.options();
4972
+ const needle = this.filter().trim().toLowerCase();
4973
+ if (!needle)
4974
+ return all;
4975
+ return all.filter(o => o.toLowerCase().includes(needle));
4976
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
4977
+ constructor() {
4978
+ // Re-resolve whenever the entity or its identifying ids change. We use
4979
+ // `untracked` around the actual mutation/IO to keep the effect's read
4980
+ // dependencies limited to the inputs that should trigger a refetch.
4981
+ effect(() => {
4982
+ const ent = this.entity();
4983
+ const rtId = this.entityRtId();
4984
+ const ckTypeId = this.entityCkTypeId();
4985
+ untracked(() => {
4986
+ if (ent) {
4987
+ this.options.set(this.resolver.extractFromEntity(ent));
4988
+ this.loading.set(false);
4989
+ return;
4990
+ }
4991
+ if (!rtId || !ckTypeId) {
4992
+ this.options.set([]);
4993
+ this.loading.set(false);
4994
+ return;
4995
+ }
4996
+ this.loading.set(true);
4997
+ void this.resolver.load(rtId, ckTypeId).then(opts => {
4998
+ // Only commit if the inputs haven't changed underneath us. We compare
4999
+ // against the current input snapshot to avoid stomping a fresher load.
5000
+ if (this.entityRtId() === rtId && this.entityCkTypeId() === ckTypeId) {
5001
+ this.options.set(opts);
5002
+ this.loading.set(false);
5003
+ }
5004
+ });
5005
+ });
5006
+ });
5007
+ }
5008
+ onValueChange(v) {
5009
+ this.value.set(v ?? '');
5010
+ }
5011
+ onFilterChange(filter) {
5012
+ this.filter.set(filter);
5013
+ this.filterChange.emit(filter);
5014
+ }
5015
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataPointPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5016
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DataPointPickerComponent, isStandalone: true, selector: "mm-data-point-picker", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: false, transformFunction: null }, entityRtId: { classPropertyName: "entityRtId", publicName: "entityRtId", isSignal: true, isRequired: false, transformFunction: null }, entityCkTypeId: { classPropertyName: "entityCkTypeId", publicName: "entityCkTypeId", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", filterChange: "filterChange" }, ngImport: i0, template: `
5017
+ <kendo-combobox
5018
+ [data]="filteredOptions()"
5019
+ [value]="value()"
5020
+ [valuePrimitive]="true"
5021
+ [allowCustom]="true"
5022
+ [filterable]="true"
5023
+ [disabled]="disabled() || loading()"
5024
+ [popupSettings]="{ appendTo: 'root', animate: true }"
5025
+ [placeholder]="placeholder()"
5026
+ (valueChange)="onValueChange($event)"
5027
+ (filterChange)="onFilterChange($event)">
5028
+ </kendo-combobox>
5029
+ @if (loading()) {
5030
+ <span class="dpp-loading">loading…</span>
5031
+ }
5032
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;gap:6px;width:100%}kendo-combobox{flex:1;min-width:0}.dpp-loading{font-size:.7rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);color:var(--kendo-color-info, #0dcaf0);font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: ComboBoxModule }, { kind: "component", type: i3$1.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5033
+ }
5034
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataPointPickerComponent, decorators: [{
5035
+ type: Component,
5036
+ args: [{ selector: 'mm-data-point-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ComboBoxModule], template: `
5037
+ <kendo-combobox
5038
+ [data]="filteredOptions()"
5039
+ [value]="value()"
5040
+ [valuePrimitive]="true"
5041
+ [allowCustom]="true"
5042
+ [filterable]="true"
5043
+ [disabled]="disabled() || loading()"
5044
+ [popupSettings]="{ appendTo: 'root', animate: true }"
5045
+ [placeholder]="placeholder()"
5046
+ (valueChange)="onValueChange($event)"
5047
+ (filterChange)="onFilterChange($event)">
5048
+ </kendo-combobox>
5049
+ @if (loading()) {
5050
+ <span class="dpp-loading">loading…</span>
5051
+ }
5052
+ `, styles: [":host{display:inline-flex;align-items:center;gap:6px;width:100%}kendo-combobox{flex:1;min-width:0}.dpp-loading{font-size:.7rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);color:var(--kendo-color-info, #0dcaf0);font-style:italic}\n"] }]
5053
+ }], ctorParameters: () => [], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: false }] }], entityRtId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityRtId", required: false }] }], entityCkTypeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityCkTypeId", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }] } });
5054
+
4738
5055
  // noinspection JSUnusedGlobalSymbols
4739
5056
  class OctoGraphQlDataSource extends DataSourceTyped {
4740
5057
  _searchFilterAttributePaths = [];
@@ -6021,7 +6338,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6021
6338
  }] });
6022
6339
 
6023
6340
  class EntitySelectorDialogComponent {
6024
- dialogRef = inject(DialogRef);
6341
+ windowRef = inject(WindowRef);
6025
6342
  treeDataSource = inject(RuntimeBrowserDataSource);
6026
6343
  data = {};
6027
6344
  selectedEntity = null;
@@ -6047,55 +6364,53 @@ class EntitySelectorDialogComponent {
6047
6364
  ckTypeId: this.selectedEntity.ckTypeId,
6048
6365
  name: this.selectedEntity.name,
6049
6366
  };
6050
- this.dialogRef.close(result);
6367
+ this.windowRef.close(result);
6051
6368
  }
6052
6369
  }
6053
6370
  onCancel() {
6054
- this.dialogRef.close();
6371
+ this.windowRef.close();
6055
6372
  }
6056
6373
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EntitySelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6057
6374
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EntitySelectorDialogComponent, isStandalone: true, selector: "mm-entity-selector-dialog", providers: [RuntimeBrowserDataSource], ngImport: i0, template: `
6058
6375
  <div class="entity-selector">
6059
- <div class="tree-section">
6060
- <mm-tree-view
6061
- [dataSource]="treeDataSource"
6062
- (nodeSelected)="onNodeSelected($event)"
6063
- ></mm-tree-view>
6064
- </div>
6376
+ <div class="entity-selector-body">
6377
+ <div class="tree-section">
6378
+ <mm-tree-view
6379
+ [dataSource]="treeDataSource"
6380
+ (nodeSelected)="onNodeSelected($event)"
6381
+ ></mm-tree-view>
6382
+ </div>
6065
6383
 
6066
- @if (selectedEntity) {
6067
- <div class="selection-preview">
6068
- <div class="preview-row">
6069
- <span class="preview-label">Name:</span>
6070
- <span class="preview-value">{{ selectedEntity.name || '\u2014' }}</span>
6071
- </div>
6072
- <div class="preview-row">
6073
- <span class="preview-label">Type:</span>
6074
- <span class="preview-value monospace">{{ selectedEntity.ckTypeId }}</span>
6384
+ @if (selectedEntity) {
6385
+ <div class="selection-preview">
6386
+ <div class="preview-row">
6387
+ <span class="preview-label">Name:</span>
6388
+ <span class="preview-value">{{ selectedEntity.name || '\u2014' }}</span>
6389
+ </div>
6390
+ <div class="preview-row">
6391
+ <span class="preview-label">Type:</span>
6392
+ <span class="preview-value monospace">{{ selectedEntity.ckTypeId }}</span>
6393
+ </div>
6394
+ <div class="preview-row">
6395
+ <span class="preview-label">RtId:</span>
6396
+ <span class="preview-value monospace">{{ selectedEntity.rtId }}</span>
6397
+ </div>
6075
6398
  </div>
6076
- <div class="preview-row">
6077
- <span class="preview-label">RtId:</span>
6078
- <span class="preview-value monospace">{{ selectedEntity.rtId }}</span>
6399
+ } @else {
6400
+ <div class="selection-hint">
6401
+ Select an entity from the tree above.
6079
6402
  </div>
6080
- </div>
6081
- }
6082
-
6083
- @if (!selectedEntity) {
6084
- <div class="selection-hint">
6085
- Select an entity from the tree above.
6086
- </div>
6087
- }
6403
+ }
6404
+ </div>
6088
6405
 
6089
6406
  <div class="dialog-actions">
6407
+ <button kendoButton (click)="onCancel()">Cancel</button>
6090
6408
  <button kendoButton themeColor="primary" [disabled]="!selectedEntity" (click)="onConfirm()">
6091
6409
  Select
6092
6410
  </button>
6093
- <button kendoButton (click)="onCancel()">
6094
- Cancel
6095
- </button>
6096
6411
  </div>
6097
6412
  </div>
6098
- `, isInline: true, styles: [".entity-selector{display:flex;flex-direction:column;height:100%;padding:12px;gap:12px}.tree-section{flex:1;min-height:0;overflow:auto;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface, #ffffff)}.selection-preview{padding:10px 12px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);display:flex;flex-direction:column;gap:4px}.preview-row{display:flex;gap:8px}.preview-row .preview-label{font-weight:600;min-width:45px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.preview-row .preview-value{font-size:.85rem}.preview-row .monospace{font-family:monospace}.selection-hint{text-align:center;padding:8px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.dialog-actions{display:flex;gap:8px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: TreeComponent, selector: "mm-tree-view", inputs: ["dataSource"], outputs: ["nodeSelected", "nodeClick", "nodeDoubleClick", "nodeDrop", "expand", "collapse"] }] });
6413
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}.entity-selector{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box}.entity-selector-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.tree-section{flex:1;min-height:0;overflow:auto;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface, #ffffff)}.selection-preview{padding:10px 12px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);display:flex;flex-direction:column;gap:4px;flex-shrink:0}.preview-row{display:flex;gap:8px}.preview-row .preview-label{font-weight:600;min-width:45px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.preview-row .preview-value{font-size:.85rem}.preview-row .monospace{font-family:monospace}.selection-hint{text-align:center;padding:8px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem;flex-shrink:0}.dialog-actions{flex:0 0 auto;display:flex;gap:8px;justify-content:flex-end;padding:10px 14px;border-top:1px solid var(--kendo-color-border, #dee2e6);background:var(--kendo-color-surface, transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: TreeComponent, selector: "mm-tree-view", inputs: ["dataSource"], outputs: ["nodeSelected", "nodeClick", "nodeDoubleClick", "nodeDrop", "expand", "collapse"] }] });
6099
6414
  }
6100
6415
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EntitySelectorDialogComponent, decorators: [{
6101
6416
  type: Component,
@@ -6105,66 +6420,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6105
6420
  TreeComponent,
6106
6421
  ], providers: [RuntimeBrowserDataSource], template: `
6107
6422
  <div class="entity-selector">
6108
- <div class="tree-section">
6109
- <mm-tree-view
6110
- [dataSource]="treeDataSource"
6111
- (nodeSelected)="onNodeSelected($event)"
6112
- ></mm-tree-view>
6113
- </div>
6423
+ <div class="entity-selector-body">
6424
+ <div class="tree-section">
6425
+ <mm-tree-view
6426
+ [dataSource]="treeDataSource"
6427
+ (nodeSelected)="onNodeSelected($event)"
6428
+ ></mm-tree-view>
6429
+ </div>
6114
6430
 
6115
- @if (selectedEntity) {
6116
- <div class="selection-preview">
6117
- <div class="preview-row">
6118
- <span class="preview-label">Name:</span>
6119
- <span class="preview-value">{{ selectedEntity.name || '\u2014' }}</span>
6120
- </div>
6121
- <div class="preview-row">
6122
- <span class="preview-label">Type:</span>
6123
- <span class="preview-value monospace">{{ selectedEntity.ckTypeId }}</span>
6431
+ @if (selectedEntity) {
6432
+ <div class="selection-preview">
6433
+ <div class="preview-row">
6434
+ <span class="preview-label">Name:</span>
6435
+ <span class="preview-value">{{ selectedEntity.name || '\u2014' }}</span>
6436
+ </div>
6437
+ <div class="preview-row">
6438
+ <span class="preview-label">Type:</span>
6439
+ <span class="preview-value monospace">{{ selectedEntity.ckTypeId }}</span>
6440
+ </div>
6441
+ <div class="preview-row">
6442
+ <span class="preview-label">RtId:</span>
6443
+ <span class="preview-value monospace">{{ selectedEntity.rtId }}</span>
6444
+ </div>
6124
6445
  </div>
6125
- <div class="preview-row">
6126
- <span class="preview-label">RtId:</span>
6127
- <span class="preview-value monospace">{{ selectedEntity.rtId }}</span>
6446
+ } @else {
6447
+ <div class="selection-hint">
6448
+ Select an entity from the tree above.
6128
6449
  </div>
6129
- </div>
6130
- }
6131
-
6132
- @if (!selectedEntity) {
6133
- <div class="selection-hint">
6134
- Select an entity from the tree above.
6135
- </div>
6136
- }
6450
+ }
6451
+ </div>
6137
6452
 
6138
6453
  <div class="dialog-actions">
6454
+ <button kendoButton (click)="onCancel()">Cancel</button>
6139
6455
  <button kendoButton themeColor="primary" [disabled]="!selectedEntity" (click)="onConfirm()">
6140
6456
  Select
6141
6457
  </button>
6142
- <button kendoButton (click)="onCancel()">
6143
- Cancel
6144
- </button>
6145
6458
  </div>
6146
6459
  </div>
6147
- `, styles: [".entity-selector{display:flex;flex-direction:column;height:100%;padding:12px;gap:12px}.tree-section{flex:1;min-height:0;overflow:auto;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface, #ffffff)}.selection-preview{padding:10px 12px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);display:flex;flex-direction:column;gap:4px}.preview-row{display:flex;gap:8px}.preview-row .preview-label{font-weight:600;min-width:45px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.preview-row .preview-value{font-size:.85rem}.preview-row .monospace{font-family:monospace}.selection-hint{text-align:center;padding:8px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.dialog-actions{display:flex;gap:8px;justify-content:flex-end}\n"] }]
6460
+ `, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}.entity-selector{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box}.entity-selector-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.tree-section{flex:1;min-height:0;overflow:auto;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface, #ffffff)}.selection-preview{padding:10px 12px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);display:flex;flex-direction:column;gap:4px;flex-shrink:0}.preview-row{display:flex;gap:8px}.preview-row .preview-label{font-weight:600;min-width:45px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.preview-row .preview-value{font-size:.85rem}.preview-row .monospace{font-family:monospace}.selection-hint{text-align:center;padding:8px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem;flex-shrink:0}.dialog-actions{flex:0 0 auto;display:flex;gap:8px;justify-content:flex-end;padding:10px 14px;border-top:1px solid var(--kendo-color-border, #dee2e6);background:var(--kendo-color-surface, transparent)}\n"] }]
6148
6461
  }] });
6149
6462
 
6463
+ const DIALOG_KEY$1 = 'entity-selector';
6464
+ const DEFAULT_SIZE$1 = { width: 600, height: 680 };
6465
+ const MIN_SIZE = { width: 480, height: 540 };
6150
6466
  class EntitySelectorDialogService {
6151
- dialogService = inject(DialogService);
6467
+ windowService = inject(WindowService);
6468
+ windowStateService = inject(WindowStateService);
6469
+ /**
6470
+ * Opens the entity selector as a Kendo Window so it stacks correctly above
6471
+ * other Kendo Windows (e.g. the Mapping-Edit dialog calls this from its
6472
+ * own Window — Kendo Dialogs would land underneath because Window and
6473
+ * Dialog use different z-index ranges).
6474
+ */
6152
6475
  async openEntitySelector(data) {
6153
- const dialogRef = this.dialogService.open({
6476
+ const size = this.windowStateService.resolveWindowSize(DIALOG_KEY$1, DEFAULT_SIZE$1, MIN_SIZE);
6477
+ const windowRef = this.windowService.open({
6154
6478
  content: EntitySelectorDialogComponent,
6155
- width: 500,
6156
- height: 600,
6157
- minWidth: 400,
6158
- minHeight: 400,
6479
+ width: size.width,
6480
+ height: size.height,
6481
+ minWidth: MIN_SIZE.width,
6482
+ minHeight: MIN_SIZE.height,
6483
+ resizable: true,
6159
6484
  title: data?.title ?? 'Select Target Entity',
6160
6485
  });
6161
- const contentRef = dialogRef.content.instance;
6162
- if (data) {
6163
- contentRef.data = data;
6486
+ this.windowStateService.applyModalBehavior(DIALOG_KEY$1, windowRef);
6487
+ const contentRef = windowRef.content;
6488
+ if (data && contentRef?.instance) {
6489
+ contentRef.instance.data = data;
6164
6490
  }
6165
6491
  try {
6166
- const result = await firstValueFrom(dialogRef.result);
6167
- if (result instanceof DialogCloseResult) {
6492
+ const result = await firstValueFrom(windowRef.result);
6493
+ if (result instanceof WindowCloseResult) {
6168
6494
  return { confirmed: false };
6169
6495
  }
6170
6496
  if (result && typeof result === 'object' && 'rtId' in result) {
@@ -6980,7 +7306,7 @@ class FieldFilterEditorComponent {
6980
7306
  </div>
6981
7307
  }
6982
7308
  </div>
6983
- `, isInline: true, styles: [".field-filter-editor{display:flex;flex-direction:column;gap:10px}.attribute-options{display:flex;align-items:center;gap:16px;font-size:.85rem}.inline-checkbox{display:flex;align-items:center;gap:6px;cursor:pointer;font-weight:400}.inline-field{display:flex;align-items:center;gap:6px;font-weight:400}.depth-input{width:80px}.loading-hint{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.toolbar{display:flex;gap:10px;margin-bottom:5px}.filter-grid{border:1px solid #d5d5d5}.filter-grid ::ng-deep .k-grid-header .k-header{font-weight:600}.checkbox-column{text-align:center}.attribute-dropdown,.operator-dropdown,.attribute-input,.value-input{width:100%}.attribute-item{display:flex;justify-content:space-between;align-items:center;width:100%}.attribute-path{flex:1}.attribute-type{font-size:11px;color:#888;margin-left:8px;padding:2px 6px;background:#f0f0f0;border-radius:3px}.empty-state{padding:40px;text-align:center;border:1px dashed;border-radius:8px;font-family:Montserrat,sans-serif;font-size:.9rem}.empty-state p{margin:0}.value-cell{display:flex;gap:4px;align-items:center}.value-cell .value-input{flex:1}.variable-toggle{flex-shrink:0}.variable-item{display:flex;flex-direction:column;gap:2px}.variable-name{font-family:monospace;font-weight:500}.variable-label{font-size:11px;color:var(--kendo-color-subtle, #888)}.variable-value{font-family:monospace;color:var(--kendo-color-primary, #0d6efd)}.variable-placeholder{color:var(--kendo-color-subtle, #888)}.variable-dropdown ::ng-deep .k-input-value-text{font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "directive", type: i3.HeaderTemplateDirective, selector: "[kendoGridHeaderTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "directive", type: i4.ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i5.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i5.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DateInputsModule }, { kind: "component", type: i7.DateTimePickerComponent, selector: "kendo-datetimepicker", inputs: ["focusableId", "weekDaysFormat", "showOtherMonthDays", "value", "format", "twoDigitYearMax", "tabindex", "disabledDates", "popupSettings", "adaptiveTitle", "adaptiveSubtitle", "disabled", "readonly", "readOnlyInput", "cancelButton", "formatPlaceholder", "placeholder", "steps", "focusedDate", "calendarType", "animateCalendarNavigation", "weekNumber", "min", "max", "rangeValidation", "disabledDatesValidation", "incompleteDateValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "clearButton", "autoFill", "adaptiveMode", "inputAttributes", "defaultTab", "size", "rounded", "fillMode", "headerTemplate", "footerTemplate", "footer"], outputs: ["valueChange", "open", "close", "focus", "blur", "escape"], exportAs: ["kendo-datetimepicker"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: PopupModule }, { kind: "ngmodule", type: IntlModule }] });
7309
+ `, isInline: true, styles: [".field-filter-editor{display:flex;flex-direction:column;gap:10px}.attribute-options{display:flex;align-items:center;gap:16px;font-size:.85rem}.inline-checkbox{display:flex;align-items:center;gap:6px;cursor:pointer;font-weight:400}.inline-field{display:flex;align-items:center;gap:6px;font-weight:400}.depth-input{width:80px}.loading-hint{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.toolbar{display:flex;gap:10px;margin-bottom:5px}.filter-grid{border:1px solid #d5d5d5}.filter-grid ::ng-deep .k-grid-header .k-header{font-weight:600}.checkbox-column{text-align:center}.attribute-dropdown,.operator-dropdown,.attribute-input,.value-input{width:100%}.attribute-item{display:flex;justify-content:space-between;align-items:center;width:100%}.attribute-path{flex:1}.attribute-type{font-size:11px;color:#888;margin-left:8px;padding:2px 6px;background:#f0f0f0;border-radius:3px}.empty-state{padding:40px;text-align:center;border:1px dashed;border-radius:8px;font-family:Montserrat,sans-serif;font-size:.9rem}.empty-state p{margin:0}.value-cell{display:flex;gap:4px;align-items:center}.value-cell .value-input{flex:1}.variable-toggle{flex-shrink:0}.variable-item{display:flex;flex-direction:column;gap:2px}.variable-name{font-family:monospace;font-weight:500}.variable-label{font-size:11px;color:var(--kendo-color-subtle, #888)}.variable-value{font-family:monospace;color:var(--kendo-color-primary, #0d6efd)}.variable-placeholder{color:var(--kendo-color-subtle, #888)}.variable-dropdown ::ng-deep .k-input-value-text{font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: "ngmodule", type: GridModule }, { kind: "component", type: i3.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i3.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i3.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i3.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "directive", type: i3.HeaderTemplateDirective, selector: "[kendoGridHeaderTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i3$1.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i3$1.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i3$1.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "directive", type: i3$1.ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i5.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i5.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DateInputsModule }, { kind: "component", type: i7.DateTimePickerComponent, selector: "kendo-datetimepicker", inputs: ["focusableId", "weekDaysFormat", "showOtherMonthDays", "value", "format", "twoDigitYearMax", "tabindex", "disabledDates", "popupSettings", "adaptiveTitle", "adaptiveSubtitle", "disabled", "readonly", "readOnlyInput", "cancelButton", "formatPlaceholder", "placeholder", "steps", "focusedDate", "calendarType", "animateCalendarNavigation", "weekNumber", "min", "max", "rangeValidation", "disabledDatesValidation", "incompleteDateValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "clearButton", "autoFill", "adaptiveMode", "inputAttributes", "defaultTab", "size", "rounded", "fillMode", "headerTemplate", "footerTemplate", "footer"], outputs: ["valueChange", "open", "close", "focus", "blur", "escape"], exportAs: ["kendo-datetimepicker"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: PopupModule }, { kind: "ngmodule", type: IntlModule }] });
6984
7310
  }
6985
7311
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FieldFilterEditorComponent, decorators: [{
6986
7312
  type: Component,
@@ -7541,47 +7867,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
7541
7867
  }]
7542
7868
  }], ctorParameters: () => [{ type: i1$3.Apollo }] });
7543
7869
 
7544
- const GetRuntimeEntityByIdDocumentDto = gql `
7545
- query getRuntimeEntityById($rtId: OctoObjectId!, $ckTypeId: String!) {
7546
- runtime {
7547
- runtimeEntities(ckId: $ckTypeId, rtId: $rtId) {
7548
- items {
7549
- rtId
7550
- ckTypeId
7551
- rtWellKnownName
7552
- rtCreationDateTime
7553
- rtChangedDateTime
7554
- attributes(resolveEnumValuesToNames: true) {
7555
- items {
7556
- attributeName
7557
- value
7558
- }
7559
- }
7560
- associations {
7561
- definitions(direction: ANY, first: 0) {
7562
- totalCount
7563
- }
7564
- }
7565
- }
7566
- }
7567
- }
7568
- }
7569
- `;
7570
- class GetRuntimeEntityByIdDtoGQL extends i1$3.Query {
7571
- document = GetRuntimeEntityByIdDocumentDto;
7572
- constructor(apollo) {
7573
- super(apollo);
7574
- }
7575
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
7576
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, providedIn: 'root' });
7577
- }
7578
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetRuntimeEntityByIdDtoGQL, decorators: [{
7579
- type: Injectable,
7580
- args: [{
7581
- providedIn: 'root'
7582
- }]
7583
- }], ctorParameters: () => [{ type: i1$3.Apollo }] });
7584
-
7585
7870
  const GetBinaryInfoDocumentDto = gql `
7586
7871
  query getBinaryInfo($rtId: OctoObjectId!) {
7587
7872
  runtime {
@@ -7816,7 +8101,14 @@ const DEFAULT_RUNTIME_BROWSER_MESSAGES = {
7816
8101
 
7817
8102
  class DataMappingListComponent {
7818
8103
  mappings = [];
7819
- sourceDataPoints = [];
8104
+ /**
8105
+ * The runtime entity whose data points feed the Source Data Point picker.
8106
+ * Typically the entity displayed in the surrounding detail pane (each mapping
8107
+ * row shares the same source — the entity being inspected). The picker reads
8108
+ * the States/DataPoints RecordArray off this entity to populate its options.
8109
+ * When null, the picker falls back to the {@link DEFAULT_DATA_POINT} default.
8110
+ */
8111
+ sourceEntity = null;
7820
8112
  /**
7821
8113
  * Optional expression validator function. When provided, expressions are validated
7822
8114
  * on change and feedback (error or preview) is shown below the expression field.
@@ -7827,6 +8119,11 @@ class DataMappingListComponent {
7827
8119
  addMapping = new EventEmitter();
7828
8120
  removeMapping = new EventEmitter();
7829
8121
  selectTarget = new EventEmitter();
8122
+ /**
8123
+ * @deprecated Source attribute selection is now handled inline by
8124
+ * {@link DataPointPickerComponent}; this output is no longer emitted.
8125
+ * Kept on the public surface so existing host bindings keep compiling.
8126
+ */
7830
8127
  selectSourceAttribute = new EventEmitter();
7831
8128
  selectTargetAttribute = new EventEmitter();
7832
8129
  mappingChanged = new EventEmitter();
@@ -7855,7 +8152,7 @@ class DataMappingListComponent {
7855
8152
  trashIcon = trashIcon;
7856
8153
  linkIcon = hyperlinkOpenIcon;
7857
8154
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataMappingListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7858
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DataMappingListComponent, isStandalone: true, selector: "mm-data-mapping-list", inputs: { mappings: "mappings", sourceDataPoints: "sourceDataPoints", expressionValidator: "expressionValidator" }, outputs: { addMapping: "addMapping", removeMapping: "removeMapping", selectTarget: "selectTarget", selectSourceAttribute: "selectSourceAttribute", selectTargetAttribute: "selectTargetAttribute", mappingChanged: "mappingChanged", navigateToTarget: "navigateToTarget", saveAll: "saveAll" }, ngImport: i0, template: `
8155
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DataMappingListComponent, isStandalone: true, selector: "mm-data-mapping-list", inputs: { mappings: "mappings", sourceEntity: "sourceEntity", expressionValidator: "expressionValidator" }, outputs: { addMapping: "addMapping", removeMapping: "removeMapping", selectTarget: "selectTarget", selectSourceAttribute: "selectSourceAttribute", selectTargetAttribute: "selectTargetAttribute", mappingChanged: "mappingChanged", navigateToTarget: "navigateToTarget", saveAll: "saveAll" }, ngImport: i0, template: `
7859
8156
  <div class="mapping-list">
7860
8157
  <div class="mapping-toolbar">
7861
8158
  <button kendoButton themeColor="primary" size="small" [svgIcon]="plusIcon"
@@ -7878,20 +8175,11 @@ class DataMappingListComponent {
7878
8175
  <div class="mapping-card-body">
7879
8176
  <div class="mapping-row">
7880
8177
  <label>Source Data Point</label>
7881
- @if (sourceDataPoints.length > 0) {
7882
- <kendo-dropdownlist
7883
- [data]="sourceDataPoints"
7884
- [value]="mapping.sourceAttributePath || 'currentValue'"
7885
- [valuePrimitive]="true"
7886
- (valueChange)="onSourceDataPointChange(mapping, $event)">
7887
- </kendo-dropdownlist>
7888
- } @else {
7889
- <div class="target-display">
7890
- <span class="target-info">{{ mapping.sourceAttributePath || '(not set)' }}</span>
7891
- <button kendoButton fillMode="flat" size="small"
7892
- (click)="selectSourceAttribute.emit(mapping)">Select...</button>
7893
- </div>
7894
- }
8178
+ <mm-data-point-picker
8179
+ [entity]="sourceEntity"
8180
+ [value]="mapping.sourceAttributePath || 'currentValue'"
8181
+ (valueChange)="onSourceDataPointChange(mapping, $event)">
8182
+ </mm-data-point-picker>
7895
8183
  </div>
7896
8184
  <div class="mapping-row">
7897
8185
  <label>Expression</label>
@@ -7956,7 +8244,7 @@ class DataMappingListComponent {
7956
8244
  </div>
7957
8245
  }
7958
8246
  </div>
7959
- `, isInline: true, styles: [".mapping-list{display:flex;flex-direction:column;gap:12px}.mapping-toolbar{display:flex;justify-content:flex-end}.mapping-empty-hint{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.mapping-card{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:6px;overflow:hidden}.mapping-card-header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-on-primary, #ffffff);background:var(--kendo-color-primary, #ff6358)}.mapping-card-header .mapping-name{flex:1}.mapping-card-body{padding:10px 12px;display:flex;flex-direction:column;gap:8px}.mapping-row{display:flex;flex-direction:column;gap:3px}.mapping-row label{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.target-display{display:flex;align-items:center;gap:8px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);min-height:30px}.target-display .target-info{flex:1;font-size:.85rem;font-family:monospace}.mapping-actions{display:flex;justify-content:flex-end;padding-top:4px}.entity-info-display{display:flex;flex-direction:column;gap:2px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa)}.entity-info-main{display:flex;align-items:center;gap:4px}.entity-name{flex:1;font-size:.85rem;font-weight:600}.entity-info-details{display:flex;align-items:center;gap:2px;font-size:.7rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.entity-detail-separator{color:var(--kendo-color-subtle, #6c757d)}.expression-feedback{font-size:.75rem;padding:2px 0;font-family:monospace}.expression-error{color:var(--kendo-color-error, #dc3545)}.expression-success{color:var(--kendo-color-success, #28a745)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: TextBoxModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "ngmodule", type: SVGIconModule }] });
8247
+ `, isInline: true, styles: [".mapping-list{display:flex;flex-direction:column;gap:12px}.mapping-toolbar{display:flex;justify-content:flex-end}.mapping-empty-hint{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.mapping-card{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:6px;overflow:hidden}.mapping-card-header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-on-primary, #ffffff);background:var(--kendo-color-primary, #ff6358)}.mapping-card-header .mapping-name{flex:1}.mapping-card-body{padding:10px 12px;display:flex;flex-direction:column;gap:8px}.mapping-row{display:flex;flex-direction:column;gap:3px}.mapping-row label{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.target-display{display:flex;align-items:center;gap:8px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);min-height:30px}.target-display .target-info{flex:1;font-size:.85rem;font-family:monospace}.mapping-actions{display:flex;justify-content:flex-end;padding-top:4px}.entity-info-display{display:flex;flex-direction:column;gap:2px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa)}.entity-info-main{display:flex;align-items:center;gap:4px}.entity-name{flex:1;font-size:.85rem;font-weight:600}.entity-info-details{display:flex;align-items:center;gap:2px;font-size:.7rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.entity-detail-separator{color:var(--kendo-color-subtle, #6c757d)}.expression-feedback{font-size:.75rem;padding:2px 0;font-family:monospace}.expression-error{color:var(--kendo-color-error, #dc3545)}.expression-success{color:var(--kendo-color-success, #28a745)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: TextBoxModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: DataPointPickerComponent, selector: "mm-data-point-picker", inputs: ["entity", "entityRtId", "entityCkTypeId", "value", "placeholder", "disabled"], outputs: ["valueChange", "filterChange"] }] });
7960
8248
  }
7961
8249
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataMappingListComponent, decorators: [{
7962
8250
  type: Component,
@@ -7964,9 +8252,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
7964
8252
  CommonModule,
7965
8253
  FormsModule,
7966
8254
  ButtonModule,
7967
- DropDownListModule,
7968
8255
  TextBoxModule,
7969
8256
  SVGIconModule,
8257
+ DataPointPickerComponent,
7970
8258
  ], template: `
7971
8259
  <div class="mapping-list">
7972
8260
  <div class="mapping-toolbar">
@@ -7990,20 +8278,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
7990
8278
  <div class="mapping-card-body">
7991
8279
  <div class="mapping-row">
7992
8280
  <label>Source Data Point</label>
7993
- @if (sourceDataPoints.length > 0) {
7994
- <kendo-dropdownlist
7995
- [data]="sourceDataPoints"
7996
- [value]="mapping.sourceAttributePath || 'currentValue'"
7997
- [valuePrimitive]="true"
7998
- (valueChange)="onSourceDataPointChange(mapping, $event)">
7999
- </kendo-dropdownlist>
8000
- } @else {
8001
- <div class="target-display">
8002
- <span class="target-info">{{ mapping.sourceAttributePath || '(not set)' }}</span>
8003
- <button kendoButton fillMode="flat" size="small"
8004
- (click)="selectSourceAttribute.emit(mapping)">Select...</button>
8005
- </div>
8006
- }
8281
+ <mm-data-point-picker
8282
+ [entity]="sourceEntity"
8283
+ [value]="mapping.sourceAttributePath || 'currentValue'"
8284
+ (valueChange)="onSourceDataPointChange(mapping, $event)">
8285
+ </mm-data-point-picker>
8007
8286
  </div>
8008
8287
  <div class="mapping-row">
8009
8288
  <label>Expression</label>
@@ -8071,7 +8350,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8071
8350
  `, styles: [".mapping-list{display:flex;flex-direction:column;gap:12px}.mapping-toolbar{display:flex;justify-content:flex-end}.mapping-empty-hint{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.mapping-card{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:6px;overflow:hidden}.mapping-card-header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-on-primary, #ffffff);background:var(--kendo-color-primary, #ff6358)}.mapping-card-header .mapping-name{flex:1}.mapping-card-body{padding:10px 12px;display:flex;flex-direction:column;gap:8px}.mapping-row{display:flex;flex-direction:column;gap:3px}.mapping-row label{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.target-display{display:flex;align-items:center;gap:8px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);min-height:30px}.target-display .target-info{flex:1;font-size:.85rem;font-family:monospace}.mapping-actions{display:flex;justify-content:flex-end;padding-top:4px}.entity-info-display{display:flex;flex-direction:column;gap:2px;padding:4px 8px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa)}.entity-info-main{display:flex;align-items:center;gap:4px}.entity-name{flex:1;font-size:.85rem;font-weight:600}.entity-info-details{display:flex;align-items:center;gap:2px;font-size:.7rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.entity-detail-separator{color:var(--kendo-color-subtle, #6c757d)}.expression-feedback{font-size:.75rem;padding:2px 0;font-family:monospace}.expression-error{color:var(--kendo-color-error, #dc3545)}.expression-success{color:var(--kendo-color-success, #28a745)}\n"] }]
8072
8351
  }], propDecorators: { mappings: [{
8073
8352
  type: Input
8074
- }], sourceDataPoints: [{
8353
+ }], sourceEntity: [{
8075
8354
  type: Input
8076
8355
  }], expressionValidator: [{
8077
8356
  type: Input
@@ -8103,7 +8382,6 @@ class EntityDetailViewComponent {
8103
8382
  }
8104
8383
  showDataMapping = true;
8105
8384
  dataMappings = [];
8106
- sourceDataPoints = [];
8107
8385
  /**
8108
8386
  * Optional expression validator function passed through to DataMappingListComponent.
8109
8387
  * When provided, mapping expressions are validated on change with visual feedback.
@@ -8115,6 +8393,11 @@ class EntityDetailViewComponent {
8115
8393
  addMappingRequested = new EventEmitter();
8116
8394
  removeMappingRequested = new EventEmitter();
8117
8395
  selectMappingTarget = new EventEmitter();
8396
+ /**
8397
+ * @deprecated The source attribute is now picked inline by the
8398
+ * DataPointPickerComponent; this output is no longer emitted.
8399
+ * Kept for binding compatibility with existing hosts.
8400
+ */
8118
8401
  selectSourceAttributeRequested = new EventEmitter();
8119
8402
  selectTargetAttributeRequested = new EventEmitter();
8120
8403
  mappingChanged = new EventEmitter();
@@ -8377,7 +8660,7 @@ class EntityDetailViewComponent {
8377
8660
  }
8378
8661
  }
8379
8662
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EntityDetailViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8380
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EntityDetailViewComponent, isStandalone: true, selector: "mm-entity-detail-view", inputs: { entity: "entity", loading: "loading", error: "error", showHeader: "showHeader", messages: "messages", showDataMapping: "showDataMapping", dataMappings: "dataMappings", sourceDataPoints: "sourceDataPoints", expressionValidator: "expressionValidator" }, outputs: { retry: "retry", propertyChange: "propertyChange", navigateToEntity: "navigateToEntity", addMappingRequested: "addMappingRequested", removeMappingRequested: "removeMappingRequested", selectMappingTarget: "selectMappingTarget", selectSourceAttributeRequested: "selectSourceAttributeRequested", selectTargetAttributeRequested: "selectTargetAttributeRequested", mappingChanged: "mappingChanged", saveAllMappingsRequested: "saveAllMappingsRequested" }, viewQueries: [{ propertyName: "associationsDataSource", first: true, predicate: ["associationsDir"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
8663
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EntityDetailViewComponent, isStandalone: true, selector: "mm-entity-detail-view", inputs: { entity: "entity", loading: "loading", error: "error", showHeader: "showHeader", messages: "messages", showDataMapping: "showDataMapping", dataMappings: "dataMappings", expressionValidator: "expressionValidator" }, outputs: { retry: "retry", propertyChange: "propertyChange", navigateToEntity: "navigateToEntity", addMappingRequested: "addMappingRequested", removeMappingRequested: "removeMappingRequested", selectMappingTarget: "selectMappingTarget", selectSourceAttributeRequested: "selectSourceAttributeRequested", selectTargetAttributeRequested: "selectTargetAttributeRequested", mappingChanged: "mappingChanged", saveAllMappingsRequested: "saveAllMappingsRequested" }, viewQueries: [{ propertyName: "associationsDataSource", first: true, predicate: ["associationsDir"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
8381
8664
  @if (loading) {
8382
8665
  <div class="loading-state">
8383
8666
  <p>{{ _messages.loadingEntityDetails }}</p>
@@ -8593,12 +8876,11 @@ class EntityDetailViewComponent {
8593
8876
  <div class="tab-content mapping-tab">
8594
8877
  <mm-data-mapping-list
8595
8878
  [mappings]="dataMappings"
8596
- [sourceDataPoints]="sourceDataPoints"
8879
+ [sourceEntity]="entity"
8597
8880
  [expressionValidator]="expressionValidator"
8598
8881
  (addMapping)="addMappingRequested.emit()"
8599
8882
  (removeMapping)="removeMappingRequested.emit($event)"
8600
8883
  (selectTarget)="selectMappingTarget.emit($event)"
8601
- (selectSourceAttribute)="selectSourceAttributeRequested.emit($event)"
8602
8884
  (selectTargetAttribute)="selectTargetAttributeRequested.emit($event)"
8603
8885
  (mappingChanged)="mappingChanged.emit($event)"
8604
8886
  (navigateToTarget)="onNavigateToMappingTarget($event)"
@@ -8611,7 +8893,7 @@ class EntityDetailViewComponent {
8611
8893
  </kendo-tabstrip>
8612
8894
  </div>
8613
8895
  }
8614
- `, isInline: true, styles: [".loading-state,.error-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;text-align:center;min-height:200px}.loading-state .error-message,.error-state .error-message{margin-bottom:16px;font-family:Roboto,sans-serif;color:#e74c3c}.entity-content{flex:1;display:flex;flex-direction:column;gap:24px}.entity-content .basic-info-card{padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.entity-content .basic-info-card:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.entity-content .basic-info-card .basic-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px}.entity-content .basic-info-card .basic-info-grid .info-item{display:flex;flex-direction:column;gap:8px}.entity-content .basic-info-card .basic-info-grid .info-item label{font-family:Montserrat,sans-serif;font-weight:600;font-size:.75rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80)}.entity-content .basic-info-card .basic-info-grid .info-item .value{font-family:Roboto Mono,monospace;font-size:.875rem;color:rgba(var(--octo-text-color),.9);word-break:break-word;background:var(--deep-sea-40);padding:8px 12px;border-radius:4px;border:1px solid var(--octo-mint-15)}.entity-content .basic-info-card .basic-info-grid .info-item.with-action .value-with-action{display:flex;align-items:center}.entity-content .basic-info-card .basic-info-grid .info-item.with-action .value-with-action .value{flex:1;font-family:Roboto Mono,monospace;font-size:.875rem;color:rgba(var(--octo-text-color),.9);word-break:break-word}.entity-content .entity-tabs{flex:1;min-height:400px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;overflow:hidden;position:relative}.entity-content .entity-tabs:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);z-index:1}.entity-content .entity-tabs ::ng-deep .k-tabstrip{background:transparent}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper{background:linear-gradient(90deg,var(--octo-mint-10),transparent);border-bottom:1px solid var(--octo-mint-20);padding-left:20px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item{background:transparent;border:none;color:rgba(var(--octo-text-color),.7);font-family:Montserrat,sans-serif;font-weight:600;font-size:.8rem;letter-spacing:.5px;text-transform:uppercase;padding:12px 20px;margin-right:4px;border-radius:4px 4px 0 0;transition:all .2s ease}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item:hover{background:var(--octo-mint-10);color:var(--octo-mint)}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item.k-active{background:linear-gradient(180deg,var(--octo-mint-20),transparent);color:var(--octo-mint);border-bottom:2px solid var(--octo-mint);box-shadow:0 0 10px var(--octo-mint-30)}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-content{background:transparent;border:none;padding:0}.entity-content .entity-tabs .tab-content{padding:20px 24px;height:100%;overflow-y:auto}.entity-content .entity-tabs .tab-content .empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 40px;text-align:center}.entity-content .entity-tabs .tab-content .empty-state kendo-svgicon{font-size:64px;margin-bottom:20px;color:var(--octo-mint-40);text-shadow:0 0 20px var(--octo-mint-30)}.entity-content .entity-tabs .tab-content .empty-state p{margin:0;font-family:Montserrat,sans-serif;font-size:.9rem;color:rgba(var(--octo-text-color),.5);letter-spacing:.5px}.entity-content .entity-tabs .tab-content.properties-tab{padding:0;height:100%}.entity-content .entity-tabs .tab-content.properties-tab mm-property-grid{display:block;height:100%;width:100%}.entity-content .entity-tabs .tab-content.associations-tab{display:flex;flex-direction:column;gap:16px;padding:0;height:100%}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:16px;padding:16px 20px;background:linear-gradient(90deg,var(--octo-mint-05),transparent);border-bottom:1px solid var(--octo-mint-20)}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group{display:flex;align-items:center;gap:10px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group label{font-family:Montserrat,sans-serif;font-weight:600;font-size:.75rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80);white-space:nowrap}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-dropdownlist{width:160px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-textbox{width:220px}.entity-content .entity-tabs .tab-content.associations-tab mm-list-view{flex:1;display:block;height:calc(100% - 70px)}.entity-content .entity-tabs .tab-content.mapping-tab{padding:12px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty{display:flex;flex-direction:column;align-items:center;padding:24px 20px;text-align:center}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty kendo-svgicon{font-size:36px;margin-bottom:10px;opacity:.5}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty p{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-config{display:flex;flex-direction:column;gap:20px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:6px;overflow:hidden}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section .section-header{padding:8px 14px;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--kendo-color-on-primary, #ffffff);background:var(--kendo-color-primary, #ff6358)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section .section-body{padding:12px 14px;display:flex;flex-direction:column;gap:10px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-field{display:flex;flex-direction:column;gap:4px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-field label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display{display:flex;align-items:center;gap:8px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display .target-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace;padding:2px 6px;border-radius:3px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display .target-name{font-weight:600;flex:1}.entity-content .entity-tabs .tab-content.mapping-tab .field-hint{font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic;line-height:1.3}.entity-content .entity-tabs .tab-content.mapping-tab .attribute-picker{display:flex;align-items:center;gap:8px;padding:4px 10px;border-radius:4px;border:1px solid var(--kendo-color-border, #dee2e6);background:var(--kendo-color-surface-alt, #f8f9fa);min-height:32px}.entity-content .entity-tabs .tab-content.mapping-tab .attribute-picker .attribute-value{flex:1;font-family:monospace;font-size:.85rem}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-actions{display:flex;gap:8px;justify-content:flex-end;padding-top:4px}@media(max-width:768px){.entity-content{gap:16px}.entity-content .basic-info-card{padding:16px 20px}.entity-content .basic-info-card .basic-info-grid{grid-template-columns:1fr;gap:16px}.entity-content .entity-tabs{min-height:300px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper{padding-left:12px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item{padding:10px 14px;font-size:.75rem}.entity-content .entity-tabs .tab-content{padding:16px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar{flex-direction:column;align-items:flex-start;gap:12px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group{width:100%}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-dropdownlist,.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-textbox{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i1$4.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i1$4.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i1$4.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$4.CardComponent, selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i1$4.CardBodyComponent, selector: "kendo-card-body" }, { kind: "component", type: i1$4.CardHeaderComponent, selector: "kendo-card-header" }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: TextBoxModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: PropertyGridComponent, selector: "mm-property-grid", inputs: ["data", "config", "showTypeColumn"], outputs: ["propertyChange", "saveRequested", "binaryDownload"] }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "rowClass", "messages", "selectable", "columns"], outputs: ["rowClicked"] }, { kind: "directive", type: EntityAssociationsDataSourceDirective, selector: "[mmEntityAssociationsDataSource]", exportAs: ["mmEntityAssociationsDataSource"] }, { kind: "component", type: DataMappingListComponent, selector: "mm-data-mapping-list", inputs: ["mappings", "sourceDataPoints", "expressionValidator"], outputs: ["addMapping", "removeMapping", "selectTarget", "selectSourceAttribute", "selectTargetAttribute", "mappingChanged", "navigateToTarget", "saveAll"] }] });
8896
+ `, isInline: true, styles: [".loading-state,.error-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;text-align:center;min-height:200px}.loading-state .error-message,.error-state .error-message{margin-bottom:16px;font-family:Roboto,sans-serif;color:#e74c3c}.entity-content{flex:1;display:flex;flex-direction:column;gap:24px}.entity-content .basic-info-card{padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.entity-content .basic-info-card:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.entity-content .basic-info-card .basic-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px}.entity-content .basic-info-card .basic-info-grid .info-item{display:flex;flex-direction:column;gap:8px}.entity-content .basic-info-card .basic-info-grid .info-item label{font-family:Montserrat,sans-serif;font-weight:600;font-size:.75rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80)}.entity-content .basic-info-card .basic-info-grid .info-item .value{font-family:Roboto Mono,monospace;font-size:.875rem;color:rgba(var(--octo-text-color),.9);word-break:break-word;background:var(--deep-sea-40);padding:8px 12px;border-radius:4px;border:1px solid var(--octo-mint-15)}.entity-content .basic-info-card .basic-info-grid .info-item.with-action .value-with-action{display:flex;align-items:center}.entity-content .basic-info-card .basic-info-grid .info-item.with-action .value-with-action .value{flex:1;font-family:Roboto Mono,monospace;font-size:.875rem;color:rgba(var(--octo-text-color),.9);word-break:break-word}.entity-content .entity-tabs{flex:1;min-height:400px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;overflow:hidden;position:relative}.entity-content .entity-tabs:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);z-index:1}.entity-content .entity-tabs ::ng-deep .k-tabstrip{background:transparent}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper{background:linear-gradient(90deg,var(--octo-mint-10),transparent);border-bottom:1px solid var(--octo-mint-20);padding-left:20px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item{background:transparent;border:none;color:rgba(var(--octo-text-color),.7);font-family:Montserrat,sans-serif;font-weight:600;font-size:.8rem;letter-spacing:.5px;text-transform:uppercase;padding:12px 20px;margin-right:4px;border-radius:4px 4px 0 0;transition:all .2s ease}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item:hover{background:var(--octo-mint-10);color:var(--octo-mint)}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item.k-active{background:linear-gradient(180deg,var(--octo-mint-20),transparent);color:var(--octo-mint);border-bottom:2px solid var(--octo-mint);box-shadow:0 0 10px var(--octo-mint-30)}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-content{background:transparent;border:none;padding:0}.entity-content .entity-tabs .tab-content{padding:20px 24px;height:100%;overflow-y:auto}.entity-content .entity-tabs .tab-content .empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 40px;text-align:center}.entity-content .entity-tabs .tab-content .empty-state kendo-svgicon{font-size:64px;margin-bottom:20px;color:var(--octo-mint-40);text-shadow:0 0 20px var(--octo-mint-30)}.entity-content .entity-tabs .tab-content .empty-state p{margin:0;font-family:Montserrat,sans-serif;font-size:.9rem;color:rgba(var(--octo-text-color),.5);letter-spacing:.5px}.entity-content .entity-tabs .tab-content.properties-tab{padding:0;height:100%}.entity-content .entity-tabs .tab-content.properties-tab mm-property-grid{display:block;height:100%;width:100%}.entity-content .entity-tabs .tab-content.associations-tab{display:flex;flex-direction:column;gap:16px;padding:0;height:100%}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:16px;padding:16px 20px;background:linear-gradient(90deg,var(--octo-mint-05),transparent);border-bottom:1px solid var(--octo-mint-20)}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group{display:flex;align-items:center;gap:10px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group label{font-family:Montserrat,sans-serif;font-weight:600;font-size:.75rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80);white-space:nowrap}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-dropdownlist{width:160px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-textbox{width:220px}.entity-content .entity-tabs .tab-content.associations-tab mm-list-view{flex:1;display:block;height:calc(100% - 70px)}.entity-content .entity-tabs .tab-content.mapping-tab{padding:12px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty{display:flex;flex-direction:column;align-items:center;padding:24px 20px;text-align:center}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty kendo-svgicon{font-size:36px;margin-bottom:10px;opacity:.5}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-empty p{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-config{display:flex;flex-direction:column;gap:20px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:6px;overflow:hidden}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section .section-header{padding:8px 14px;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--kendo-color-on-primary, #ffffff);background:var(--kendo-color-primary, #ff6358)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-section .section-body{padding:12px 14px;display:flex;flex-direction:column;gap:10px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-field{display:flex;flex-direction:column;gap:4px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-field label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display{display:flex;align-items:center;gap:8px}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display .target-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace;padding:2px 6px;border-radius:3px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6)}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-target-display .target-name{font-weight:600;flex:1}.entity-content .entity-tabs .tab-content.mapping-tab .field-hint{font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic;line-height:1.3}.entity-content .entity-tabs .tab-content.mapping-tab .attribute-picker{display:flex;align-items:center;gap:8px;padding:4px 10px;border-radius:4px;border:1px solid var(--kendo-color-border, #dee2e6);background:var(--kendo-color-surface-alt, #f8f9fa);min-height:32px}.entity-content .entity-tabs .tab-content.mapping-tab .attribute-picker .attribute-value{flex:1;font-family:monospace;font-size:.85rem}.entity-content .entity-tabs .tab-content.mapping-tab .mapping-actions{display:flex;gap:8px;justify-content:flex-end;padding-top:4px}@media(max-width:768px){.entity-content{gap:16px}.entity-content .basic-info-card{padding:16px 20px}.entity-content .basic-info-card .basic-info-grid{grid-template-columns:1fr;gap:16px}.entity-content .entity-tabs{min-height:300px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper{padding-left:12px}.entity-content .entity-tabs ::ng-deep .k-tabstrip .k-tabstrip-items-wrapper .k-tabstrip-items .k-item{padding:10px 14px;font-size:.75rem}.entity-content .entity-tabs .tab-content{padding:16px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar{flex-direction:column;align-items:flex-start;gap:12px}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group{width:100%}.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-dropdownlist,.entity-content .entity-tabs .tab-content.associations-tab .associations-toolbar .filter-group kendo-textbox{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i1$4.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i1$4.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i1$4.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$4.CardComponent, selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i1$4.CardBodyComponent, selector: "kendo-card-body" }, { kind: "component", type: i1$4.CardHeaderComponent, selector: "kendo-card-header" }, { kind: "ngmodule", type: DropDownListModule }, { kind: "component", type: i3$1.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: TextBoxModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: PropertyGridComponent, selector: "mm-property-grid", inputs: ["data", "config", "showTypeColumn"], outputs: ["propertyChange", "saveRequested", "binaryDownload"] }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "rowClass", "messages", "selectable", "columns"], outputs: ["rowClicked"] }, { kind: "directive", type: EntityAssociationsDataSourceDirective, selector: "[mmEntityAssociationsDataSource]", exportAs: ["mmEntityAssociationsDataSource"] }, { kind: "component", type: DataMappingListComponent, selector: "mm-data-mapping-list", inputs: ["mappings", "sourceEntity", "expressionValidator"], outputs: ["addMapping", "removeMapping", "selectTarget", "selectSourceAttribute", "selectTargetAttribute", "mappingChanged", "navigateToTarget", "saveAll"] }] });
8615
8897
  }
8616
8898
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EntityDetailViewComponent, decorators: [{
8617
8899
  type: Component,
@@ -8843,12 +9125,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8843
9125
  <div class="tab-content mapping-tab">
8844
9126
  <mm-data-mapping-list
8845
9127
  [mappings]="dataMappings"
8846
- [sourceDataPoints]="sourceDataPoints"
9128
+ [sourceEntity]="entity"
8847
9129
  [expressionValidator]="expressionValidator"
8848
9130
  (addMapping)="addMappingRequested.emit()"
8849
9131
  (removeMapping)="removeMappingRequested.emit($event)"
8850
9132
  (selectTarget)="selectMappingTarget.emit($event)"
8851
- (selectSourceAttribute)="selectSourceAttributeRequested.emit($event)"
8852
9133
  (selectTargetAttribute)="selectTargetAttributeRequested.emit($event)"
8853
9134
  (mappingChanged)="mappingChanged.emit($event)"
8854
9135
  (navigateToTarget)="onNavigateToMappingTarget($event)"
@@ -8876,8 +9157,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8876
9157
  type: Input
8877
9158
  }], dataMappings: [{
8878
9159
  type: Input
8879
- }], sourceDataPoints: [{
8880
- type: Input
8881
9160
  }], expressionValidator: [{
8882
9161
  type: Input
8883
9162
  }], retry: [{
@@ -9399,7 +9678,7 @@ class EntityDetailComponent {
9399
9678
  >
9400
9679
  </mm-entity-detail-view>
9401
9680
  </div>
9402
- `, isInline: true, styles: [":host{display:block;height:100%;width:100%}.entity-detail{height:100%;display:flex;flex-direction:column;padding:24px;box-sizing:border-box;overflow-y:auto}.entity-detail .entity-detail-header{display:flex;align-items:center;gap:20px;margin-bottom:24px;padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.entity-detail .entity-detail-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.entity-detail .entity-detail-header .header-info{flex:1}.entity-detail .entity-detail-header .header-info .entity-title h2{margin:0 0 8px;font-family:Montserrat,sans-serif;font-size:1.4rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color);text-shadow:0 0 10px rgba(0,0,0,.3)}.entity-detail .entity-detail-header .header-info .entity-type{margin:0;font-family:Roboto Mono,monospace;font-size:.8rem;background:var(--deep-sea-60);padding:6px 12px;border-radius:4px;border:1px solid var(--neo-cyan-30);display:inline-block;color:var(--neo-cyan)}.entity-detail mm-entity-detail-view{flex:1;display:flex;flex-direction:column;overflow-y:auto}@media(max-width:768px){.entity-detail{padding:16px}.entity-detail .entity-detail-header{flex-direction:column;align-items:flex-start;gap:12px;padding:16px 20px}.entity-detail .entity-detail-header .header-info .entity-title h2{font-size:1.1rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: EntityDetailViewComponent, selector: "mm-entity-detail-view", inputs: ["entity", "loading", "error", "showHeader", "messages", "showDataMapping", "dataMappings", "sourceDataPoints", "expressionValidator"], outputs: ["retry", "propertyChange", "navigateToEntity", "addMappingRequested", "removeMappingRequested", "selectMappingTarget", "selectSourceAttributeRequested", "selectTargetAttributeRequested", "mappingChanged", "saveAllMappingsRequested"] }] });
9681
+ `, isInline: true, styles: [":host{display:block;height:100%;width:100%}.entity-detail{height:100%;display:flex;flex-direction:column;padding:24px;box-sizing:border-box;overflow-y:auto}.entity-detail .entity-detail-header{display:flex;align-items:center;gap:20px;margin-bottom:24px;padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.entity-detail .entity-detail-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.entity-detail .entity-detail-header .header-info{flex:1}.entity-detail .entity-detail-header .header-info .entity-title h2{margin:0 0 8px;font-family:Montserrat,sans-serif;font-size:1.4rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color);text-shadow:0 0 10px rgba(0,0,0,.3)}.entity-detail .entity-detail-header .header-info .entity-type{margin:0;font-family:Roboto Mono,monospace;font-size:.8rem;background:var(--deep-sea-60);padding:6px 12px;border-radius:4px;border:1px solid var(--neo-cyan-30);display:inline-block;color:var(--neo-cyan)}.entity-detail mm-entity-detail-view{flex:1;display:flex;flex-direction:column;overflow-y:auto}@media(max-width:768px){.entity-detail{padding:16px}.entity-detail .entity-detail-header{flex-direction:column;align-items:flex-start;gap:12px;padding:16px 20px}.entity-detail .entity-detail-header .header-info .entity-title h2{font-size:1.1rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: EntityDetailViewComponent, selector: "mm-entity-detail-view", inputs: ["entity", "loading", "error", "showHeader", "messages", "showDataMapping", "dataMappings", "expressionValidator"], outputs: ["retry", "propertyChange", "navigateToEntity", "addMappingRequested", "removeMappingRequested", "selectMappingTarget", "selectSourceAttributeRequested", "selectTargetAttributeRequested", "mappingChanged", "saveAllMappingsRequested"] }] });
9403
9682
  }
9404
9683
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EntityDetailComponent, decorators: [{
9405
9684
  type: Component,
@@ -11707,7 +11986,7 @@ class AttributesGroupComponent {
11707
11986
  </kendo-card-body>
11708
11987
  </kendo-card>
11709
11988
  </div>
11710
- `, isInline: true, styles: [".attributes-form-container ::ng-deep kendo-card kendo-card-body{display:flex;flex-direction:column;gap:16px}.attributes-form-container ::ng-deep kendo-label{display:flex;flex-direction:column;gap:4px}.attributes-form-container ::ng-deep kendo-label label{text-transform:uppercase}.header-content{display:flex;flex-direction:column;gap:4px}.record-array-content,.tab-content-wrapper{display:flex;flex-direction:column;gap:16px}.record-actions-info{display:flex;flex-direction:column;gap:12px;padding:16px;background-color:var(--kendo-color-surface-alt, rgba(0, 0, 0, .05));border:1px solid var(--kendo-color-border, rgba(0, 0, 0, .1));border-radius:4px;margin-bottom:16px}.record-actions-description{margin:0;font-size:13px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6));line-height:1.5}.record-array-actions{display:flex;gap:8px;justify-content:flex-start;padding-top:0}.record-actions{margin-top:16px;display:flex;gap:8px;justify-content:flex-start;padding-top:0}.binary-linked-wrap{display:flex;flex-direction:column;gap:6px}.binary-linked-reference-hint{font-size:.75rem;color:var(--kendo-color-primary, #0f6dff);font-style:italic}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => AttributesGroupComponent), selector: "mm-attributes-group", inputs: ["ckId", "parentFormGroup", "isRecord", "initialValues"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i1.FormGroupDirective), selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i0.forwardRef(() => i1.FormControlName), selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i0.forwardRef(() => i1.FormGroupName), selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i0.forwardRef(() => i1$4.CardComponent), selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i0.forwardRef(() => i1$4.CardBodyComponent), selector: "kendo-card-body" }, { kind: "component", type: i0.forwardRef(() => i1$4.CardHeaderComponent), selector: "kendo-card-header" }, { kind: "component", type: i0.forwardRef(() => i1$4.ExpansionPanelComponent), selector: "kendo-expansionpanel", inputs: ["title", "subtitle", "disabled", "expanded", "svgExpandIcon", "svgCollapseIcon", "expandIcon", "collapseIcon", "animation"], outputs: ["expandedChange", "action", "expand", "collapse"], exportAs: ["kendoExpansionPanel"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ExpansionPanelTitleDirective), selector: "[kendoExpansionPanelTitleDirective]" }, { kind: "component", type: i0.forwardRef(() => i1$4.TabStripComponent), selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i0.forwardRef(() => i1$4.TabStripTabComponent), selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.TabContentDirective), selector: "[kendoTabContent]" }, { kind: "component", type: i0.forwardRef(() => i5.TextBoxComponent), selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i0.forwardRef(() => i5.NumericTextBoxComponent), selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "component", type: i0.forwardRef(() => i5.SwitchComponent), selector: "kendo-switch", inputs: ["focusableId", "onLabel", "offLabel", "checked", "disabled", "readonly", "tabindex", "size", "thumbRounded", "trackRounded", "tabIndex"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoSwitch"] }, { kind: "component", type: i0.forwardRef(() => i1$5.LabelComponent), selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "component", type: i0.forwardRef(() => i1$1.ButtonComponent), selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i0.forwardRef(() => i4.DropDownListComponent), selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i0.forwardRef(() => i4.MultiSelectComponent), selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "component", type: i0.forwardRef(() => i7.DateTimePickerComponent), selector: "kendo-datetimepicker", inputs: ["focusableId", "weekDaysFormat", "showOtherMonthDays", "value", "format", "twoDigitYearMax", "tabindex", "disabledDates", "popupSettings", "adaptiveTitle", "adaptiveSubtitle", "disabled", "readonly", "readOnlyInput", "cancelButton", "formatPlaceholder", "placeholder", "steps", "focusedDate", "calendarType", "animateCalendarNavigation", "weekNumber", "min", "max", "rangeValidation", "disabledDatesValidation", "incompleteDateValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "clearButton", "autoFill", "adaptiveMode", "inputAttributes", "defaultTab", "size", "rounded", "fillMode", "headerTemplate", "footerTemplate", "footer"], outputs: ["valueChange", "open", "close", "focus", "blur", "escape"], exportAs: ["kendo-datetimepicker"] }, { kind: "component", type: i0.forwardRef(() => i7.TimePickerComponent), selector: "kendo-timepicker", inputs: ["focusableId", "disabled", "readonly", "readOnlyInput", "clearButton", "format", "formatPlaceholder", "placeholder", "min", "max", "incompleteDateValidation", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "cancelButton", "nowButton", "steps", "popupSettings", "tabindex", "tabIndex", "adaptiveTitle", "adaptiveSubtitle", "rangeValidation", "adaptiveMode", "value", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "open", "close", "escape"], exportAs: ["kendo-timepicker"] }, { kind: "component", type: i0.forwardRef(() => i8.FileSelectComponent), selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }, { kind: "component", type: i0.forwardRef(() => AttributeFieldComponent), selector: "mm-attribute-field", inputs: ["attribute", "control", "baselineValue", "fieldId", "overrideLabelText", "showUndoButton", "undoButtonSize", "errorMessage", "hintText"], outputs: ["undo"] }] });
11989
+ `, isInline: true, styles: [".attributes-form-container ::ng-deep kendo-card kendo-card-body{display:flex;flex-direction:column;gap:16px}.attributes-form-container ::ng-deep kendo-label{display:flex;flex-direction:column;gap:4px}.attributes-form-container ::ng-deep kendo-label label{text-transform:uppercase}.header-content{display:flex;flex-direction:column;gap:4px}.record-array-content,.tab-content-wrapper{display:flex;flex-direction:column;gap:16px}.record-actions-info{display:flex;flex-direction:column;gap:12px;padding:16px;background-color:var(--kendo-color-surface-alt, rgba(0, 0, 0, .05));border:1px solid var(--kendo-color-border, rgba(0, 0, 0, .1));border-radius:4px;margin-bottom:16px}.record-actions-description{margin:0;font-size:13px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6));line-height:1.5}.record-array-actions{display:flex;gap:8px;justify-content:flex-start;padding-top:0}.record-actions{margin-top:16px;display:flex;gap:8px;justify-content:flex-start;padding-top:0}.binary-linked-wrap{display:flex;flex-direction:column;gap:6px}.binary-linked-reference-hint{font-size:.75rem;color:var(--kendo-color-primary, #0f6dff);font-style:italic}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => AttributesGroupComponent), selector: "mm-attributes-group", inputs: ["ckId", "parentFormGroup", "isRecord", "initialValues"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i1.FormGroupDirective), selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i0.forwardRef(() => i1.FormControlName), selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i0.forwardRef(() => i1.FormGroupName), selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: i0.forwardRef(() => i1$4.CardComponent), selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i0.forwardRef(() => i1$4.CardBodyComponent), selector: "kendo-card-body" }, { kind: "component", type: i0.forwardRef(() => i1$4.CardHeaderComponent), selector: "kendo-card-header" }, { kind: "component", type: i0.forwardRef(() => i1$4.ExpansionPanelComponent), selector: "kendo-expansionpanel", inputs: ["title", "subtitle", "disabled", "expanded", "svgExpandIcon", "svgCollapseIcon", "expandIcon", "collapseIcon", "animation"], outputs: ["expandedChange", "action", "expand", "collapse"], exportAs: ["kendoExpansionPanel"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ExpansionPanelTitleDirective), selector: "[kendoExpansionPanelTitleDirective]" }, { kind: "component", type: i0.forwardRef(() => i1$4.TabStripComponent), selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i0.forwardRef(() => i1$4.TabStripTabComponent), selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.TabContentDirective), selector: "[kendoTabContent]" }, { kind: "component", type: i0.forwardRef(() => i5.TextBoxComponent), selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i0.forwardRef(() => i5.NumericTextBoxComponent), selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "component", type: i0.forwardRef(() => i5.SwitchComponent), selector: "kendo-switch", inputs: ["focusableId", "onLabel", "offLabel", "checked", "disabled", "readonly", "tabindex", "size", "thumbRounded", "trackRounded", "tabIndex"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoSwitch"] }, { kind: "component", type: i0.forwardRef(() => i1$5.LabelComponent), selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "component", type: i0.forwardRef(() => i1$1.ButtonComponent), selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i0.forwardRef(() => i3$1.DropDownListComponent), selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i0.forwardRef(() => i3$1.MultiSelectComponent), selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "component", type: i0.forwardRef(() => i7.DateTimePickerComponent), selector: "kendo-datetimepicker", inputs: ["focusableId", "weekDaysFormat", "showOtherMonthDays", "value", "format", "twoDigitYearMax", "tabindex", "disabledDates", "popupSettings", "adaptiveTitle", "adaptiveSubtitle", "disabled", "readonly", "readOnlyInput", "cancelButton", "formatPlaceholder", "placeholder", "steps", "focusedDate", "calendarType", "animateCalendarNavigation", "weekNumber", "min", "max", "rangeValidation", "disabledDatesValidation", "incompleteDateValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "clearButton", "autoFill", "adaptiveMode", "inputAttributes", "defaultTab", "size", "rounded", "fillMode", "headerTemplate", "footerTemplate", "footer"], outputs: ["valueChange", "open", "close", "focus", "blur", "escape"], exportAs: ["kendo-datetimepicker"] }, { kind: "component", type: i0.forwardRef(() => i7.TimePickerComponent), selector: "kendo-timepicker", inputs: ["focusableId", "disabled", "readonly", "readOnlyInput", "clearButton", "format", "formatPlaceholder", "placeholder", "min", "max", "incompleteDateValidation", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "cancelButton", "nowButton", "steps", "popupSettings", "tabindex", "tabIndex", "adaptiveTitle", "adaptiveSubtitle", "rangeValidation", "adaptiveMode", "value", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "open", "close", "escape"], exportAs: ["kendo-timepicker"] }, { kind: "component", type: i0.forwardRef(() => i8.FileSelectComponent), selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }, { kind: "component", type: i0.forwardRef(() => AttributeFieldComponent), selector: "mm-attribute-field", inputs: ["attribute", "control", "baselineValue", "fieldId", "overrideLabelText", "showUndoButton", "undoButtonSize", "errorMessage", "hintText"], outputs: ["undo"] }] });
11711
11990
  }
11712
11991
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AttributesGroupComponent, decorators: [{
11713
11992
  type: Component,
@@ -12762,9 +13041,10 @@ class RuntimeBrowserDetailsComponent {
12762
13041
  deleteEntitiesGQL = inject(DeleteEntitiesDtoGQL);
12763
13042
  entitySelectorDialog = inject(EntitySelectorDialogService);
12764
13043
  attributeSelectorDialog = inject(AttributeSelectorDialogService);
12765
- // Data Mapping state (list of DataPointMapping entities)
13044
+ // Data Mapping state (list of DataPointMapping entities). Source data points
13045
+ // for the mapping rows are now resolved inline by DataPointPickerComponent
13046
+ // from the displayed RtEntity — no precomputed string list maintained here.
12766
13047
  dataMappings = [];
12767
- sourceDataPoints = [];
12768
13048
  /**
12769
13049
  * Optional expression validator function passed through to EntityDetailViewComponent → DataMappingListComponent.
12770
13050
  */
@@ -12885,7 +13165,6 @@ class RuntimeBrowserDetailsComponent {
12885
13165
  this.error = this._messages.couldNotLoadEntityDetails;
12886
13166
  }
12887
13167
  else {
12888
- this.extractSourceDataPoints();
12889
13168
  await this.loadDataMappings();
12890
13169
  }
12891
13170
  }
@@ -13291,100 +13570,28 @@ class RuntimeBrowserDetailsComponent {
13291
13570
  // Actual persistence happens on "Save All".
13292
13571
  }
13293
13572
  /**
13294
- * Extracts DataPoint names from the entity's States/DataPoints RecordArray attribute.
13295
- * Provides them as dropdown options for sourceAttributePath selection.
13573
+ * Opens attribute selector for the source entity's attributes.
13296
13574
  */
13297
- extractSourceDataPoints() {
13298
- this.sourceDataPoints = ['currentValue'];
13575
+ async onSelectSourceAttribute(mapping) {
13299
13576
  const entity = this.getEntityForDisplay();
13300
- if (!entity?.attributes?.items)
13577
+ if (!entity?.ckTypeId)
13301
13578
  return;
13302
- // Look for a RecordArray attribute (States, DataPoints, etc.) - case insensitive
13303
- const statesAttr = entity.attributes.items.find((a) => {
13304
- const name = a?.attributeName?.toLowerCase();
13305
- return name === 'states' || name === 'datapoints';
13306
- });
13307
- if (!statesAttr?.value)
13579
+ const result = await this.attributeSelectorDialog.openAttributeSelector(entity.ckTypeId, mapping.sourceAttributePath ? [mapping.sourceAttributePath] : undefined, 'Select Source Attribute', true, undefined, false, undefined, true);
13580
+ if (result.confirmed && result.selectedAttributes.length > 0) {
13581
+ mapping.sourceAttributePath = result.selectedAttributes[0].attributePath;
13582
+ this.dataMappings = [...this.dataMappings];
13583
+ }
13584
+ }
13585
+ /**
13586
+ * Opens attribute selector for the target entity's attributes.
13587
+ */
13588
+ async onSelectTargetAttribute(mapping) {
13589
+ if (!mapping.targetCkTypeId)
13308
13590
  return;
13309
- let records;
13310
- if (Array.isArray(statesAttr.value)) {
13311
- records = statesAttr.value;
13312
- }
13313
- else if (typeof statesAttr.value === 'string') {
13314
- // RecordArray might come as JSON string from GraphQL
13315
- try {
13316
- const parsed = JSON.parse(statesAttr.value);
13317
- if (Array.isArray(parsed)) {
13318
- records = parsed;
13319
- }
13320
- else {
13321
- return;
13322
- }
13323
- }
13324
- catch {
13325
- return;
13326
- }
13327
- }
13328
- else {
13329
- return;
13330
- }
13331
- // Extract Name from each record.
13332
- // GraphQL returns records as: { ckRecordId, attributes: [{attributeName, value}, ...] }
13333
- const names = [];
13334
- for (const record of records) {
13335
- if (record && typeof record === 'object') {
13336
- const r = record;
13337
- const attrs = r['attributes'];
13338
- let name;
13339
- if (Array.isArray(attrs)) {
13340
- // GraphQL format: attributes is array of {attributeName, value}
13341
- const nameEntry = attrs
13342
- .find((a) => a.attributeName === 'name' || a.attributeName === 'Name');
13343
- if (nameEntry?.value && typeof nameEntry.value === 'string') {
13344
- name = nameEntry.value;
13345
- }
13346
- }
13347
- else if (attrs && typeof attrs === 'object') {
13348
- // Pipeline/MongoDB format: attributes is object {Name: "...", ...}
13349
- const attrObj = attrs;
13350
- name = (attrObj['Name'] ?? attrObj['name'] ?? attrObj['stateName']);
13351
- }
13352
- if (!name) {
13353
- // Flat format: {name: "...", ...}
13354
- name = (r['Name'] ?? r['name']);
13355
- }
13356
- if (typeof name === 'string' && name.length > 0) {
13357
- names.push(name);
13358
- }
13359
- }
13360
- }
13361
- if (names.length > 0) {
13362
- this.sourceDataPoints = ['currentValue', ...names.sort()];
13363
- }
13364
- }
13365
- /**
13366
- * Opens attribute selector for the source entity's attributes.
13367
- */
13368
- async onSelectSourceAttribute(mapping) {
13369
- const entity = this.getEntityForDisplay();
13370
- if (!entity?.ckTypeId)
13371
- return;
13372
- const result = await this.attributeSelectorDialog.openAttributeSelector(entity.ckTypeId, mapping.sourceAttributePath ? [mapping.sourceAttributePath] : undefined, 'Select Source Attribute', true, undefined, false, undefined, true);
13373
- if (result.confirmed && result.selectedAttributes.length > 0) {
13374
- mapping.sourceAttributePath = result.selectedAttributes[0].attributePath;
13375
- this.dataMappings = [...this.dataMappings];
13376
- }
13377
- }
13378
- /**
13379
- * Opens attribute selector for the target entity's attributes.
13380
- */
13381
- async onSelectTargetAttribute(mapping) {
13382
- if (!mapping.targetCkTypeId)
13383
- return;
13384
- const result = await this.attributeSelectorDialog.openAttributeSelector(mapping.targetCkTypeId, mapping.targetAttributePath ? [mapping.targetAttributePath] : undefined, 'Select Target Attribute', true, undefined, false, undefined, true);
13385
- if (result.confirmed && result.selectedAttributes.length > 0) {
13386
- mapping.targetAttributePath = result.selectedAttributes[0].attributePath;
13387
- this.dataMappings = [...this.dataMappings];
13591
+ const result = await this.attributeSelectorDialog.openAttributeSelector(mapping.targetCkTypeId, mapping.targetAttributePath ? [mapping.targetAttributePath] : undefined, 'Select Target Attribute', true, undefined, false, undefined, true);
13592
+ if (result.confirmed && result.selectedAttributes.length > 0) {
13593
+ mapping.targetAttributePath = result.selectedAttributes[0].attributePath;
13594
+ this.dataMappings = [...this.dataMappings];
13388
13595
  }
13389
13596
  }
13390
13597
  /**
@@ -13489,7 +13696,6 @@ class RuntimeBrowserDetailsComponent {
13489
13696
  [messages]="_messages"
13490
13697
  [showDataMapping]="showDataMapping"
13491
13698
  [dataMappings]="dataMappings"
13492
- [sourceDataPoints]="sourceDataPoints"
13493
13699
  [expressionValidator]="expressionValidator"
13494
13700
  (retry)="loadFullEntityDetails()"
13495
13701
  (navigateToEntity)="
@@ -13589,7 +13795,7 @@ class RuntimeBrowserDetailsComponent {
13589
13795
  }
13590
13796
  }
13591
13797
  </div>
13592
- `, isInline: true, styles: [":host{display:block;height:100%;width:100%}.runtime-browser-details{display:flex;flex-direction:column;flex:1;min-height:0;padding:24px;box-sizing:border-box;overflow-y:auto;background:linear-gradient(180deg,var(--surface-elevated) 0%,var(--deep-sea) 100%)}.runtime-browser-details .no-selection{display:flex;align-items:center;justify-content:center;flex:1;min-height:0}.runtime-browser-details .no-selection .placeholder-content{text-align:center;max-width:400px;padding:40px;background:linear-gradient(135deg,var(--iron-navy-80),var(--surface-elevated-90));border:1px solid var(--octo-mint-30);border-radius:8px 24px;box-shadow:0 0 30px var(--octo-mint-10),inset 0 0 40px var(--octo-mint-03)}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon{margin-bottom:24px}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon .k-icon{font-size:64px;color:var(--octo-mint-50);text-shadow:0 0 20px var(--octo-mint-50);animation:lcars-icon-pulse 3s ease-in-out infinite}.runtime-browser-details .no-selection .placeholder-content h3{margin:0 0 12px;font-family:Montserrat,sans-serif;font-size:1.4rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--octo-mint);text-shadow:0 0 15px var(--octo-mint-50)}.runtime-browser-details .no-selection .placeholder-content p{margin:0;font-family:Montserrat,sans-serif;font-size:.9rem;line-height:1.6;color:rgba(var(--octo-text-color),.7);letter-spacing:.5px}.runtime-browser-details .entity-details{height:100%;display:flex;flex-direction:column;overflow:hidden}.runtime-browser-details .entity-details .details-header{display:flex;align-items:center;gap:16px;padding:16px 20px 16px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;margin-bottom:20px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.runtime-browser-details .entity-details .details-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.runtime-browser-details .entity-details .details-header .entity-icon{font-size:28px;color:var(--octo-mint);text-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .details-header .entity-title{flex:1}.runtime-browser-details .entity-details .details-header .entity-title h3{margin:0 0 6px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .details-header .entity-title .entity-type{margin:0;font-family:Roboto Mono,monospace;font-size:.8rem;color:var(--neo-cyan);background:var(--deep-sea-60);padding:4px 10px;border-radius:4px;border:1px solid var(--neo-cyan-30);display:inline-block}.runtime-browser-details .entity-details mm-entity-detail-view{flex:1;display:flex;flex-direction:column;overflow-y:auto}.runtime-browser-details .entity-details .ck-model-details,.runtime-browser-details .entity-details .ck-models-root{padding:24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative}.runtime-browser-details .entity-details .ck-model-details:before,.runtime-browser-details .entity-details .ck-models-root:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .ck-model-details h3,.runtime-browser-details .entity-details .ck-models-root h3{margin:0 0 20px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .ck-model-details p,.runtime-browser-details .entity-details .ck-models-root p{margin:0 0 12px;font-family:Roboto,sans-serif;font-size:.9rem;color:rgba(var(--octo-text-color),.85)}.runtime-browser-details .entity-details .ck-model-details p strong,.runtime-browser-details .entity-details .ck-models-root p strong{font-family:Montserrat,sans-serif;font-weight:600;font-size:.8rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80);margin-right:12px}.runtime-browser-details .entity-details .ck-model-details p.info-text,.runtime-browser-details .entity-details .ck-models-root p.info-text{color:rgba(var(--octo-text-color),.6);font-style:italic;margin-top:24px;padding:16px 20px;background:linear-gradient(135deg,var(--neo-cyan-10),var(--neo-cyan-05));border:1px solid var(--neo-cyan-30);border-radius:4px 12px}.runtime-browser-details .entity-details .ck-type-details{display:flex;flex-direction:column;height:100%;padding:0}.runtime-browser-details .entity-details .ck-type-details .type-header{margin-bottom:20px;padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative}.runtime-browser-details .entity-details .ck-type-details .type-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .ck-type-details .type-header h3{margin:0 0 12px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata{display:flex;gap:10px;align-items:center}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge{padding:6px 14px;border-radius:4px;font-family:Montserrat,sans-serif;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge.abstract{background:linear-gradient(135deg,var(--royal-violet-30),var(--royal-violet-15));border:1px solid var(--royal-violet-50);color:var(--royal-violet-light-20);box-shadow:0 0 10px var(--royal-violet-30)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge.final{background:linear-gradient(135deg,var(--toffee-30),var(--toffee-15));border:1px solid var(--toffee-50);color:var(--toffee);box-shadow:0 0 10px var(--toffee-30)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .base-type{font-family:Roboto Mono,monospace;font-size:.8rem;color:var(--neo-cyan);background:var(--deep-sea-60);padding:4px 10px;border-radius:4px;border:1px solid var(--neo-cyan-30)}.runtime-browser-details .entity-details .ck-type-details .entities-table{flex:1;display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;padding:20px}.runtime-browser-details .entity-details .ck-type-details .entities-table h4{margin:0 0 16px;font-family:Montserrat,sans-serif;font-size:1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-mint)}.runtime-browser-details .entity-details .ck-type-details .entities-table mm-list-view{flex:1;display:flex;flex-direction:column;overflow:hidden}@keyframes lcars-icon-pulse{0%,to{opacity:.5;transform:scale(1)}50%{opacity:.8;transform:scale(1.05)}}@media(max-width:768px){.runtime-browser-details{padding:16px}.runtime-browser-details .no-selection .placeholder-content{padding:24px}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon .k-icon{font-size:48px}.runtime-browser-details .no-selection .placeholder-content h3{font-size:1.1rem}.runtime-browser-details .entity-details .details-header{flex-direction:column;align-items:flex-start;gap:12px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: EntityDetailViewComponent, selector: "mm-entity-detail-view", inputs: ["entity", "loading", "error", "showHeader", "messages", "showDataMapping", "dataMappings", "sourceDataPoints", "expressionValidator"], outputs: ["retry", "propertyChange", "navigateToEntity", "addMappingRequested", "removeMappingRequested", "selectMappingTarget", "selectSourceAttributeRequested", "selectTargetAttributeRequested", "mappingChanged", "saveAllMappingsRequested"] }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "rowClass", "messages", "selectable", "columns"], outputs: ["rowClicked"] }, { kind: "directive", type: CkTypeEntitiesDataSourceDirective, selector: "[mmCkTypeEntitiesDataSource]", exportAs: ["mmCkTypeEntitiesDataSource"] }, { kind: "component", type: CreateEditorComponent, selector: "mm-create-editor-component", inputs: ["createInput", "messages"], outputs: ["createOutput", "cancelRequested"] }, { kind: "component", type: UpdateEditorComponent, selector: "mm-update-editor-component", inputs: ["updateInput", "messages"], outputs: ["updateOutput", "cancelRequested"] }] });
13798
+ `, isInline: true, styles: [":host{display:block;height:100%;width:100%}.runtime-browser-details{display:flex;flex-direction:column;flex:1;min-height:0;padding:24px;box-sizing:border-box;overflow-y:auto;background:linear-gradient(180deg,var(--surface-elevated) 0%,var(--deep-sea) 100%)}.runtime-browser-details .no-selection{display:flex;align-items:center;justify-content:center;flex:1;min-height:0}.runtime-browser-details .no-selection .placeholder-content{text-align:center;max-width:400px;padding:40px;background:linear-gradient(135deg,var(--iron-navy-80),var(--surface-elevated-90));border:1px solid var(--octo-mint-30);border-radius:8px 24px;box-shadow:0 0 30px var(--octo-mint-10),inset 0 0 40px var(--octo-mint-03)}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon{margin-bottom:24px}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon .k-icon{font-size:64px;color:var(--octo-mint-50);text-shadow:0 0 20px var(--octo-mint-50);animation:lcars-icon-pulse 3s ease-in-out infinite}.runtime-browser-details .no-selection .placeholder-content h3{margin:0 0 12px;font-family:Montserrat,sans-serif;font-size:1.4rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--octo-mint);text-shadow:0 0 15px var(--octo-mint-50)}.runtime-browser-details .no-selection .placeholder-content p{margin:0;font-family:Montserrat,sans-serif;font-size:.9rem;line-height:1.6;color:rgba(var(--octo-text-color),.7);letter-spacing:.5px}.runtime-browser-details .entity-details{height:100%;display:flex;flex-direction:column;overflow:hidden}.runtime-browser-details .entity-details .details-header{display:flex;align-items:center;gap:16px;padding:16px 20px 16px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;margin-bottom:20px;position:relative;box-shadow:0 4px 20px #0006,0 0 15px var(--octo-mint-08)}.runtime-browser-details .entity-details .details-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50);border-radius:4px 0 0 4px}.runtime-browser-details .entity-details .details-header .entity-icon{font-size:28px;color:var(--octo-mint);text-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .details-header .entity-title{flex:1}.runtime-browser-details .entity-details .details-header .entity-title h3{margin:0 0 6px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .details-header .entity-title .entity-type{margin:0;font-family:Roboto Mono,monospace;font-size:.8rem;color:var(--neo-cyan);background:var(--deep-sea-60);padding:4px 10px;border-radius:4px;border:1px solid var(--neo-cyan-30);display:inline-block}.runtime-browser-details .entity-details mm-entity-detail-view{flex:1;display:flex;flex-direction:column;overflow-y:auto}.runtime-browser-details .entity-details .ck-model-details,.runtime-browser-details .entity-details .ck-models-root{padding:24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative}.runtime-browser-details .entity-details .ck-model-details:before,.runtime-browser-details .entity-details .ck-models-root:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .ck-model-details h3,.runtime-browser-details .entity-details .ck-models-root h3{margin:0 0 20px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .ck-model-details p,.runtime-browser-details .entity-details .ck-models-root p{margin:0 0 12px;font-family:Roboto,sans-serif;font-size:.9rem;color:rgba(var(--octo-text-color),.85)}.runtime-browser-details .entity-details .ck-model-details p strong,.runtime-browser-details .entity-details .ck-models-root p strong{font-family:Montserrat,sans-serif;font-weight:600;font-size:.8rem;letter-spacing:.5px;text-transform:uppercase;color:var(--octo-mint-80);margin-right:12px}.runtime-browser-details .entity-details .ck-model-details p.info-text,.runtime-browser-details .entity-details .ck-models-root p.info-text{color:rgba(var(--octo-text-color),.6);font-style:italic;margin-top:24px;padding:16px 20px;background:linear-gradient(135deg,var(--neo-cyan-10),var(--neo-cyan-05));border:1px solid var(--neo-cyan-30);border-radius:4px 12px}.runtime-browser-details .entity-details .ck-type-details{display:flex;flex-direction:column;height:100%;padding:0}.runtime-browser-details .entity-details .ck-type-details .type-header{margin-bottom:20px;padding:20px 24px;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;position:relative}.runtime-browser-details .entity-details .ck-type-details .type-header:before{content:\"\";position:absolute;top:0;left:0;width:4px;height:100%;background:linear-gradient(180deg,var(--octo-mint),var(--neo-cyan),var(--royal-violet));box-shadow:0 0 10px var(--octo-mint-50)}.runtime-browser-details .entity-details .ck-type-details .type-header h3{margin:0 0 12px;font-family:Montserrat,sans-serif;font-size:1.1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-text-color)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata{display:flex;gap:10px;align-items:center}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge{padding:6px 14px;border-radius:4px;font-family:Montserrat,sans-serif;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge.abstract{background:linear-gradient(135deg,var(--royal-violet-30),var(--royal-violet-15));border:1px solid var(--royal-violet-50);color:var(--royal-violet-light-20);box-shadow:0 0 10px var(--royal-violet-30)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .badge.final{background:linear-gradient(135deg,var(--toffee-30),var(--toffee-15));border:1px solid var(--toffee-50);color:var(--toffee);box-shadow:0 0 10px var(--toffee-30)}.runtime-browser-details .entity-details .ck-type-details .type-header .type-metadata .base-type{font-family:Roboto Mono,monospace;font-size:.8rem;color:var(--neo-cyan);background:var(--deep-sea-60);padding:4px 10px;border-radius:4px;border:1px solid var(--neo-cyan-30)}.runtime-browser-details .entity-details .ck-type-details .entities-table{flex:1;display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--iron-navy),var(--surface-elevated));border:1px solid var(--octo-mint-30);border-radius:4px 16px;padding:20px}.runtime-browser-details .entity-details .ck-type-details .entities-table h4{margin:0 0 16px;font-family:Montserrat,sans-serif;font-size:1rem;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--octo-mint)}.runtime-browser-details .entity-details .ck-type-details .entities-table mm-list-view{flex:1;display:flex;flex-direction:column;overflow:hidden}@keyframes lcars-icon-pulse{0%,to{opacity:.5;transform:scale(1)}50%{opacity:.8;transform:scale(1.05)}}@media(max-width:768px){.runtime-browser-details{padding:16px}.runtime-browser-details .no-selection .placeholder-content{padding:24px}.runtime-browser-details .no-selection .placeholder-content .placeholder-icon .k-icon{font-size:48px}.runtime-browser-details .no-selection .placeholder-content h3{font-size:1.1rem}.runtime-browser-details .entity-details .details-header{flex-direction:column;align-items:flex-start;gap:12px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: EntityDetailViewComponent, selector: "mm-entity-detail-view", inputs: ["entity", "loading", "error", "showHeader", "messages", "showDataMapping", "dataMappings", "expressionValidator"], outputs: ["retry", "propertyChange", "navigateToEntity", "addMappingRequested", "removeMappingRequested", "selectMappingTarget", "selectSourceAttributeRequested", "selectTargetAttributeRequested", "mappingChanged", "saveAllMappingsRequested"] }, { kind: "component", type: ListViewComponent, selector: "mm-list-view", inputs: ["pageSize", "skip", "rowIsClickable", "showRowCheckBoxes", "showRowSelectAllCheckBox", "contextMenuType", "leftToolbarActions", "rightToolbarActions", "actionCommandItems", "contextMenuCommandItems", "excelExportFileName", "pdfExportFileName", "pageable", "sortable", "rowFilterEnabled", "searchTextBoxEnabled", "rowClass", "messages", "selectable", "columns"], outputs: ["rowClicked"] }, { kind: "directive", type: CkTypeEntitiesDataSourceDirective, selector: "[mmCkTypeEntitiesDataSource]", exportAs: ["mmCkTypeEntitiesDataSource"] }, { kind: "component", type: CreateEditorComponent, selector: "mm-create-editor-component", inputs: ["createInput", "messages"], outputs: ["createOutput", "cancelRequested"] }, { kind: "component", type: UpdateEditorComponent, selector: "mm-update-editor-component", inputs: ["updateInput", "messages"], outputs: ["updateOutput", "cancelRequested"] }] });
13593
13799
  }
13594
13800
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RuntimeBrowserDetailsComponent, decorators: [{
13595
13801
  type: Component,
@@ -13643,7 +13849,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
13643
13849
  [messages]="_messages"
13644
13850
  [showDataMapping]="showDataMapping"
13645
13851
  [dataMappings]="dataMappings"
13646
- [sourceDataPoints]="sourceDataPoints"
13647
13852
  [expressionValidator]="expressionValidator"
13648
13853
  (retry)="loadFullEntityDetails()"
13649
13854
  (navigateToEntity)="
@@ -15207,6 +15412,2135 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
15207
15412
  type: Output
15208
15413
  }] } });
15209
15414
 
15415
+ const GetLatestValidationExecutionDocumentDto = gql `
15416
+ query getLatestValidationExecution($pipelineRtId: OctoObjectId!, $pipelineCkTypeId: String!, $executesRoleId: String!, $executionCkTypeId: String!) {
15417
+ runtime {
15418
+ runtimeEntities(rtId: $pipelineRtId, ckId: $pipelineCkTypeId) {
15419
+ items {
15420
+ rtId
15421
+ ckTypeId
15422
+ associations {
15423
+ executions: targets(
15424
+ roleId: $executesRoleId
15425
+ ckId: $executionCkTypeId
15426
+ direction: INBOUND
15427
+ first: 1
15428
+ sortOrder: [{attributePath: "CompletedAt", sortOrder: DESCENDING}]
15429
+ ) {
15430
+ items {
15431
+ rtId
15432
+ ckTypeId
15433
+ attributes(resolveEnumValuesToNames: true) {
15434
+ items {
15435
+ attributeName
15436
+ value
15437
+ }
15438
+ }
15439
+ }
15440
+ }
15441
+ }
15442
+ }
15443
+ }
15444
+ }
15445
+ }
15446
+ `;
15447
+ class GetLatestValidationExecutionDtoGQL extends i1$3.Query {
15448
+ document = GetLatestValidationExecutionDocumentDto;
15449
+ constructor(apollo) {
15450
+ super(apollo);
15451
+ }
15452
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetLatestValidationExecutionDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
15453
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetLatestValidationExecutionDtoGQL, providedIn: 'root' });
15454
+ }
15455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetLatestValidationExecutionDtoGQL, decorators: [{
15456
+ type: Injectable,
15457
+ args: [{
15458
+ providedIn: 'root'
15459
+ }]
15460
+ }], ctorParameters: () => [{ type: i1$3.Apollo }] });
15461
+
15462
+ const GetNodeMappingsDocumentDto = gql `
15463
+ query getNodeMappings($rtId: OctoObjectId!, $ckTypeId: String!, $mapsToRoleId: String!, $mapsFromRoleId: String!, $mappingCkTypeId: String!) {
15464
+ runtime {
15465
+ runtimeEntities(rtId: $rtId, ckId: $ckTypeId) {
15466
+ items {
15467
+ rtId
15468
+ ckTypeId
15469
+ associations {
15470
+ mappings: targets(
15471
+ roleId: $mapsToRoleId
15472
+ ckId: $mappingCkTypeId
15473
+ direction: INBOUND
15474
+ ) {
15475
+ totalCount
15476
+ items {
15477
+ rtId
15478
+ ckTypeId
15479
+ attributes(resolveEnumValuesToNames: true) {
15480
+ items {
15481
+ attributeName
15482
+ value
15483
+ }
15484
+ }
15485
+ associations {
15486
+ sources: definitions(roleId: $mapsFromRoleId, direction: OUTBOUND, first: 1) {
15487
+ items {
15488
+ targetRtId
15489
+ targetCkTypeId
15490
+ }
15491
+ }
15492
+ }
15493
+ }
15494
+ }
15495
+ }
15496
+ }
15497
+ }
15498
+ }
15499
+ }
15500
+ `;
15501
+ class GetNodeMappingsDtoGQL extends i1$3.Query {
15502
+ document = GetNodeMappingsDocumentDto;
15503
+ constructor(apollo) {
15504
+ super(apollo);
15505
+ }
15506
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetNodeMappingsDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
15507
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetNodeMappingsDtoGQL, providedIn: 'root' });
15508
+ }
15509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetNodeMappingsDtoGQL, decorators: [{
15510
+ type: Injectable,
15511
+ args: [{
15512
+ providedIn: 'root'
15513
+ }]
15514
+ }], ctorParameters: () => [{ type: i1$3.Apollo }] });
15515
+
15516
+ const GetOrphanCandidatesDocumentDto = gql `
15517
+ query getOrphanCandidates($ckTypeId: String!, $mapsFromRoleId: String!, $mappingCkTypeId: String!, $childRoleId: String!, $childCkTypeId: String!, $first: Int, $after: String, $searchFilter: SearchFilter) {
15518
+ runtime {
15519
+ runtimeEntities(
15520
+ ckId: $ckTypeId
15521
+ first: $first
15522
+ after: $after
15523
+ searchFilter: $searchFilter
15524
+ ) {
15525
+ totalCount
15526
+ items {
15527
+ rtId
15528
+ ckTypeId
15529
+ rtWellKnownName
15530
+ attributes(attributeNames: ["name", "description"]) {
15531
+ items {
15532
+ attributeName
15533
+ value
15534
+ }
15535
+ }
15536
+ associations {
15537
+ mappings: targets(
15538
+ roleId: $mapsFromRoleId
15539
+ ckId: $mappingCkTypeId
15540
+ direction: INBOUND
15541
+ first: 1
15542
+ ) {
15543
+ totalCount
15544
+ }
15545
+ parent: targets(
15546
+ roleId: $childRoleId
15547
+ ckId: $childCkTypeId
15548
+ direction: OUTBOUND
15549
+ first: 1
15550
+ ) {
15551
+ items {
15552
+ rtId
15553
+ ckTypeId
15554
+ rtWellKnownName
15555
+ attributes(attributeNames: ["name"]) {
15556
+ items {
15557
+ attributeName
15558
+ value
15559
+ }
15560
+ }
15561
+ associations {
15562
+ parent: targets(
15563
+ roleId: $childRoleId
15564
+ ckId: $childCkTypeId
15565
+ direction: OUTBOUND
15566
+ first: 1
15567
+ ) {
15568
+ items {
15569
+ rtId
15570
+ ckTypeId
15571
+ rtWellKnownName
15572
+ attributes(attributeNames: ["name"]) {
15573
+ items {
15574
+ attributeName
15575
+ value
15576
+ }
15577
+ }
15578
+ associations {
15579
+ parent: targets(
15580
+ roleId: $childRoleId
15581
+ ckId: $childCkTypeId
15582
+ direction: OUTBOUND
15583
+ first: 1
15584
+ ) {
15585
+ items {
15586
+ rtId
15587
+ ckTypeId
15588
+ rtWellKnownName
15589
+ attributes(attributeNames: ["name"]) {
15590
+ items {
15591
+ attributeName
15592
+ value
15593
+ }
15594
+ }
15595
+ }
15596
+ }
15597
+ }
15598
+ }
15599
+ }
15600
+ }
15601
+ }
15602
+ }
15603
+ }
15604
+ }
15605
+ }
15606
+ }
15607
+ }
15608
+ `;
15609
+ class GetOrphanCandidatesDtoGQL extends i1$3.Query {
15610
+ document = GetOrphanCandidatesDocumentDto;
15611
+ constructor(apollo) {
15612
+ super(apollo);
15613
+ }
15614
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetOrphanCandidatesDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
15615
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetOrphanCandidatesDtoGQL, providedIn: 'root' });
15616
+ }
15617
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetOrphanCandidatesDtoGQL, decorators: [{
15618
+ type: Injectable,
15619
+ args: [{
15620
+ providedIn: 'root'
15621
+ }]
15622
+ }], ctorParameters: () => [{ type: i1$3.Apollo }] });
15623
+
15624
+ const GetMappingCoverageNodeDocumentDto = gql `
15625
+ query getMappingCoverageNode($rtId: OctoObjectId!, $ckTypeId: String!, $childRoleId: String!, $childCkTypeId: String!, $mappingRoleId: String!, $mappingCkTypeId: String!) {
15626
+ runtime {
15627
+ runtimeEntities(rtId: $rtId, ckId: $ckTypeId) {
15628
+ items {
15629
+ rtId
15630
+ ckTypeId
15631
+ rtWellKnownName
15632
+ attributes(attributeNames: ["name", "description"]) {
15633
+ items {
15634
+ attributeName
15635
+ value
15636
+ }
15637
+ }
15638
+ associations {
15639
+ ownMappings: targets(
15640
+ roleId: $mappingRoleId
15641
+ ckId: $mappingCkTypeId
15642
+ direction: INBOUND
15643
+ ) {
15644
+ totalCount
15645
+ }
15646
+ children: targets(
15647
+ roleId: $childRoleId
15648
+ ckId: $childCkTypeId
15649
+ direction: INBOUND
15650
+ ) {
15651
+ totalCount
15652
+ items {
15653
+ rtId
15654
+ ckTypeId
15655
+ attributes(attributeNames: ["name", "description"]) {
15656
+ items {
15657
+ attributeName
15658
+ value
15659
+ }
15660
+ }
15661
+ associations {
15662
+ grandChildren: targets(
15663
+ roleId: $childRoleId
15664
+ ckId: $childCkTypeId
15665
+ direction: INBOUND
15666
+ ) {
15667
+ totalCount
15668
+ }
15669
+ mappings: targets(
15670
+ roleId: $mappingRoleId
15671
+ ckId: $mappingCkTypeId
15672
+ direction: INBOUND
15673
+ ) {
15674
+ totalCount
15675
+ }
15676
+ }
15677
+ }
15678
+ }
15679
+ }
15680
+ }
15681
+ }
15682
+ }
15683
+ }
15684
+ `;
15685
+ class GetMappingCoverageNodeDtoGQL extends i1$3.Query {
15686
+ document = GetMappingCoverageNodeDocumentDto;
15687
+ constructor(apollo) {
15688
+ super(apollo);
15689
+ }
15690
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetMappingCoverageNodeDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
15691
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetMappingCoverageNodeDtoGQL, providedIn: 'root' });
15692
+ }
15693
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GetMappingCoverageNodeDtoGQL, decorators: [{
15694
+ type: Injectable,
15695
+ args: [{
15696
+ providedIn: 'root'
15697
+ }]
15698
+ }], ctorParameters: () => [{ type: i1$3.Apollo }] });
15699
+
15700
+ /**
15701
+ * Generic hierarchy data source for the Mapping Coverage Tree.
15702
+ *
15703
+ * Configure with `setRoot(...)` and `setConfig(...)` before passing to a
15704
+ * `<mm-tree-view>` instance. The data source resolves child nodes through the
15705
+ * configured `childRoleId` / `childCkTypeId` and decorates each item with the
15706
+ * number of inbound mappings (via `mappingRoleId`).
15707
+ */
15708
+ class MappingCoverageTreeDataSource extends HierarchyDataSourceBase {
15709
+ getCoverageNodeGQL = inject(GetMappingCoverageNodeDtoGQL);
15710
+ _root = null;
15711
+ _config = null;
15712
+ _rootMappingCount = 0;
15713
+ _validationMap = new Map();
15714
+ setRoot(root) {
15715
+ this._root = root;
15716
+ }
15717
+ setConfig(config) {
15718
+ this._config = config;
15719
+ }
15720
+ /**
15721
+ * Inject the validation report (rtId → status detail) loaded from the
15722
+ * latest PipelineExecution. Pass an empty map to clear the overlay.
15723
+ * The caller must trigger a tree refresh after updating the map.
15724
+ */
15725
+ setValidationMap(map) {
15726
+ this._validationMap = map;
15727
+ }
15728
+ getValidationMap() {
15729
+ return this._validationMap;
15730
+ }
15731
+ getRoot() {
15732
+ return this._root;
15733
+ }
15734
+ getRootMappingCount() {
15735
+ return this._rootMappingCount;
15736
+ }
15737
+ async fetchRootNodes() {
15738
+ if (!this._root || !this._config) {
15739
+ return [];
15740
+ }
15741
+ const result = await this.queryNode(this._root.rtId, this._root.ckTypeId);
15742
+ if (!result) {
15743
+ // Fall back: show the root even if we could not load its children counts.
15744
+ return [this.buildItem(this._root, true, 0, true)];
15745
+ }
15746
+ this._rootMappingCount = result.ownMappingCount;
15747
+ const hasChildren = result.children.length > 0;
15748
+ return [this.buildItem(result.entity, true, result.ownMappingCount, hasChildren)];
15749
+ }
15750
+ async fetchChildren(item) {
15751
+ if (!this._config) {
15752
+ return [];
15753
+ }
15754
+ const result = await this.queryNode(item.item.rtId, item.item.ckTypeId);
15755
+ if (!result) {
15756
+ return [];
15757
+ }
15758
+ return result.children.map(child => this.buildItem({ rtId: child.rtId, ckTypeId: child.ckTypeId, name: child.name, description: child.description }, false, child.mappingCount, child.hasGrandChildren));
15759
+ }
15760
+ /**
15761
+ * Reloads the coverage payload (mapping count / hasChildren flag) for a single
15762
+ * entity. Used after CRUD operations on mappings so the badge updates without
15763
+ * collapsing the surrounding subtree.
15764
+ */
15765
+ async refreshNode(rtId, ckTypeId) {
15766
+ const result = await this.queryNode(rtId, ckTypeId);
15767
+ if (!result) {
15768
+ return null;
15769
+ }
15770
+ const detail = this._validationMap.get(rtId) ?? null;
15771
+ return {
15772
+ ...result.entity,
15773
+ mappingCount: result.ownMappingCount,
15774
+ hasChildren: result.children.length > 0,
15775
+ isRoot: this._root?.rtId === rtId,
15776
+ validationStatus: detail?.status ?? null,
15777
+ validationDetail: detail,
15778
+ };
15779
+ }
15780
+ buildItem(entity, isRoot, mappingCount, hasChildren) {
15781
+ const detail = this._validationMap.get(entity.rtId) ?? null;
15782
+ const payload = {
15783
+ ...entity,
15784
+ mappingCount,
15785
+ hasChildren,
15786
+ isRoot,
15787
+ validationStatus: detail?.status ?? null,
15788
+ validationDetail: detail,
15789
+ };
15790
+ const text = formatNodeLabel(entity, mappingCount, detail);
15791
+ const tooltip = buildTooltip(entity, detail);
15792
+ // Use the subtree rollup status so info/ok parents reveal hidden warnings
15793
+ // or errors below them. Falls back to own status when subtree info isn't
15794
+ // in the report (older backend) or no detail at all.
15795
+ const iconStatus = detail?.subtreeStatus ?? detail?.status ?? null;
15796
+ const icon = resolveIcon(iconStatus, isRoot);
15797
+ return new TreeItemDataTyped(entity.rtId, text, tooltip, payload, icon, hasChildren, false);
15798
+ }
15799
+ async queryNode(rtId, ckTypeId) {
15800
+ if (!this._config)
15801
+ return null;
15802
+ try {
15803
+ const data = await firstValueFrom(this.getCoverageNodeGQL
15804
+ .fetch({
15805
+ variables: {
15806
+ rtId,
15807
+ ckTypeId,
15808
+ childRoleId: this._config.childRoleId,
15809
+ childCkTypeId: this._config.childCkTypeId,
15810
+ mappingRoleId: this._config.mappingRoleId,
15811
+ mappingCkTypeId: this._config.mappingCkTypeId,
15812
+ },
15813
+ fetchPolicy: 'network-only',
15814
+ })
15815
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items?.[0])));
15816
+ if (!data?.rtId || !data.ckTypeId) {
15817
+ return null;
15818
+ }
15819
+ const entity = {
15820
+ rtId: data.rtId,
15821
+ ckTypeId: data.ckTypeId,
15822
+ name: readAttr$1(data.attributes?.items, 'name') ?? data.rtWellKnownName ?? data.rtId,
15823
+ description: readAttr$1(data.attributes?.items, 'description') ?? undefined,
15824
+ };
15825
+ const ownMappingCount = data.associations?.ownMappings?.totalCount ?? 0;
15826
+ const childItems = data.associations?.children?.items ?? [];
15827
+ const children = childItems
15828
+ .filter((c) => !!c && !!c.rtId && !!c.ckTypeId)
15829
+ .map(c => ({
15830
+ rtId: c.rtId,
15831
+ ckTypeId: c.ckTypeId,
15832
+ name: readAttr$1(c.attributes?.items, 'name') ?? c.rtId,
15833
+ description: readAttr$1(c.attributes?.items, 'description') ?? undefined,
15834
+ mappingCount: c.associations?.mappings?.totalCount ?? 0,
15835
+ hasGrandChildren: (c.associations?.grandChildren?.totalCount ?? 0) > 0,
15836
+ }));
15837
+ return { entity, ownMappingCount, children };
15838
+ }
15839
+ catch (error) {
15840
+ console.error('Failed to load coverage node', error);
15841
+ return null;
15842
+ }
15843
+ }
15844
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingCoverageTreeDataSource, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
15845
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingCoverageTreeDataSource });
15846
+ }
15847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingCoverageTreeDataSource, decorators: [{
15848
+ type: Injectable
15849
+ }] });
15850
+ function readAttr$1(items, name) {
15851
+ if (!items)
15852
+ return null;
15853
+ const target = name.toLowerCase();
15854
+ for (const item of items) {
15855
+ if (item?.attributeName != null && item.attributeName.toLowerCase() === target && item.value != null) {
15856
+ return String(item.value);
15857
+ }
15858
+ }
15859
+ return null;
15860
+ }
15861
+ function formatNodeLabel(entity, mappingCount, detail) {
15862
+ const base = entity.name || entity.rtId;
15863
+ const countSuffix = mappingCount > 0 ? ` [${mappingCount}]` : '';
15864
+ if (detail && (detail.missingRequired.length > 0 || detail.missingRecommended.length > 0)) {
15865
+ const missing = [...detail.missingRequired, ...detail.missingRecommended].join(', ');
15866
+ return `${base}${countSuffix} — missing: ${missing}`;
15867
+ }
15868
+ return `${base}${countSuffix}`;
15869
+ }
15870
+ function buildTooltip(entity, detail) {
15871
+ const head = entity.description ?? `${entity.ckTypeId}@${entity.rtId}`;
15872
+ if (!detail)
15873
+ return head;
15874
+ const lines = [head, `Status: ${detail.status}`];
15875
+ // Flag subtree problems on `info` / `ok` parents so the user knows to drill
15876
+ // in. Only show when the subtree is strictly worse than the node itself.
15877
+ const counts = detail.subtreeCounts;
15878
+ if (counts) {
15879
+ const errBelow = counts.error - (detail.status === 'error' ? 1 : 0);
15880
+ const warnBelow = counts.warning - (detail.status === 'warning' ? 1 : 0);
15881
+ if (errBelow > 0 || warnBelow > 0) {
15882
+ const parts = [];
15883
+ if (errBelow > 0)
15884
+ parts.push(`${errBelow} error${errBelow === 1 ? '' : 's'}`);
15885
+ if (warnBelow > 0)
15886
+ parts.push(`${warnBelow} warning${warnBelow === 1 ? '' : 's'}`);
15887
+ lines.push(`Below: ${parts.join(', ')}`);
15888
+ }
15889
+ }
15890
+ if (detail.missingRequired.length > 0) {
15891
+ lines.push(`Missing required: ${detail.missingRequired.join(', ')}`);
15892
+ }
15893
+ if (detail.missingRecommended.length > 0) {
15894
+ lines.push(`Missing recommended: ${detail.missingRecommended.join(', ')}`);
15895
+ }
15896
+ if (detail.present.length > 0) {
15897
+ lines.push(`Present: ${detail.present.join(', ')}`);
15898
+ }
15899
+ return lines.join('\n');
15900
+ }
15901
+ /**
15902
+ * Wraps a Kendo SVG icon's <path> markup in a coloured group so the rendered
15903
+ * icon picks up a semantic colour at the SVG layer — no CSS hooks needed.
15904
+ *
15905
+ * Kendo's `kendo-svgicon` writes the icon content as-is inside an outer
15906
+ * `<svg>`, and the path has no fill of its own (so it inherits currentColor).
15907
+ * Wrapping in `<g fill="…">` forces a literal colour and survives the
15908
+ * surrounding `color: inherit` in the tree template.
15909
+ *
15910
+ * We embed the hex value rather than a CSS variable because SVG attributes
15911
+ * inside the content string aren't resolved by CSS — they're literal.
15912
+ */
15913
+ function colorize(icon, color) {
15914
+ return {
15915
+ name: `${icon.name}-${color.replace('#', '')}`,
15916
+ viewBox: icon.viewBox,
15917
+ content: `<g fill="${color}">${icon.content}</g>`,
15918
+ };
15919
+ }
15920
+ // Semantic palette — matches the badge colours used in the toolbar summary
15921
+ // (badge-error/-warning/-ok/-info) so the legend and the tree agree.
15922
+ const STATUS_COLORS = {
15923
+ ok: '#28a745', // success green
15924
+ warning: '#ffc107', // amber
15925
+ error: '#dc3545', // red
15926
+ info: '#0dcaf0', // info cyan
15927
+ };
15928
+ const COLORED_STATUS_ICONS = {
15929
+ ok: colorize(checkCircleIcon, STATUS_COLORS.ok),
15930
+ warning: colorize(warningCircleIcon, STATUS_COLORS.warning),
15931
+ error: colorize(exclamationCircleIcon, STATUS_COLORS.error),
15932
+ info: colorize(infoCircleIcon, STATUS_COLORS.info),
15933
+ };
15934
+ function resolveIcon(status, isRoot) {
15935
+ if (status && COLORED_STATUS_ICONS[status]) {
15936
+ return COLORED_STATUS_ICONS[status];
15937
+ }
15938
+ return isRoot ? folderIcon : gridIcon;
15939
+ }
15940
+
15941
+ /**
15942
+ * Focused single-mapping editor dialog. The host renders this via
15943
+ * `MappingEditDialogService.open(...)` and awaits the resulting promise:
15944
+ * `confirmed=true` means the user saved (caller persists), `confirmed=false`
15945
+ * means cancel.
15946
+ */
15947
+ class MappingEditDialogComponent {
15948
+ windowRef = inject(WindowRef);
15949
+ entitySelector = inject(EntitySelectorDialogService);
15950
+ attributeSelectorDialog = inject(AttributeSelectorDialogService);
15951
+ attributeService = inject(AttributeSelectorService);
15952
+ icons = {
15953
+ link: hyperlinkOpenIcon,
15954
+ browse: folderOpenIcon,
15955
+ regenerate: arrowRotateCwIcon,
15956
+ };
15957
+ /** Set by the service before the dialog content is shown. */
15958
+ data = {
15959
+ mapping: this.emptyValue(),
15960
+ };
15961
+ /** Reactive working copy — the form binds to this. */
15962
+ model = signal(this.emptyValue(), ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
15963
+ /** Target CK-type attribute catalogue. The source side is now handled by
15964
+ * the dedicated {@link DataPointPickerComponent}, which loads runtime state
15965
+ * names from the entity itself instead of CK schema attributes. */
15966
+ targetAttributes = signal([], ...(ngDevMode ? [{ debugName: "targetAttributes" }] : /* istanbul ignore next */ []));
15967
+ targetFilter = signal('', ...(ngDevMode ? [{ debugName: "targetFilter" }] : /* istanbul ignore next */ []));
15968
+ targetAttributesLoading = signal(false, ...(ngDevMode ? [{ debugName: "targetAttributesLoading" }] : /* istanbul ignore next */ []));
15969
+ targetAttributeList = computed(() => filterAttributes(this.targetAttributes(), this.targetFilter()), ...(ngDevMode ? [{ debugName: "targetAttributeList" }] : /* istanbul ignore next */ []));
15970
+ /**
15971
+ * Effective target CK type id — prefers the value picked by the user via
15972
+ * {@link pickTarget}; falls back to the optional {@link MappingEditDialogData.targetCkTypeId}
15973
+ * legacy default. Drives the Target Attribute Path autocomplete dropdown.
15974
+ */
15975
+ effectiveTargetCkTypeId = computed(() => this.model().targetCkTypeId ?? this.data.targetCkTypeId, ...(ngDevMode ? [{ debugName: "effectiveTargetCkTypeId" }] : /* istanbul ignore next */ []));
15976
+ initialise(data) {
15977
+ this.data = data;
15978
+ // Seed mapping.targetCkTypeId from the legacy data field when the caller
15979
+ // hasn't set it yet — covers the tree-context flow where the dialog was
15980
+ // opened for a fixed target node and the value was passed via data.
15981
+ const seededTargetCkTypeId = data.mapping.targetCkTypeId ?? data.targetCkTypeId;
15982
+ this.model.set({
15983
+ ...data.mapping,
15984
+ targetCkTypeId: seededTargetCkTypeId,
15985
+ _originalSourceRtId: data.mapping.sourceRtId,
15986
+ _originalSourceCkTypeId: data.mapping.sourceCkTypeId,
15987
+ _originalTargetRtId: data.mapping.targetRtId,
15988
+ _originalTargetCkTypeId: data.mapping.targetCkTypeId,
15989
+ });
15990
+ if (seededTargetCkTypeId) {
15991
+ void this.loadTargetAttributes(seededTargetCkTypeId);
15992
+ }
15993
+ }
15994
+ /**
15995
+ * Enabled when at least one labelled side of the mapping is filled enough
15996
+ * to produce a meaningful name. We require *some* identifying info on each
15997
+ * end so the generated name isn't just "(unset) → (unset)".
15998
+ */
15999
+ canGenerateName() {
16000
+ const m = this.model();
16001
+ const sourceLabel = m.sourceName || m.sourceRtId;
16002
+ const targetLabel = m.targetName || m.targetRtId;
16003
+ return !!(sourceLabel && targetLabel);
16004
+ }
16005
+ /**
16006
+ * Writes a deterministic, human-readable name into the mapping based on the
16007
+ * current source/target selection. Format:
16008
+ * `{sourceName} {sourcePath} → {targetName} {targetPath}`
16009
+ * Falls back to rtId fragments when names are missing. The user can still
16010
+ * edit the result freely — this just gives them a sensible starting point
16011
+ * so they don't have to invent a name from scratch when finishing an
16012
+ * orphan-tab mapping.
16013
+ */
16014
+ generateName() {
16015
+ const m = this.model();
16016
+ const source = describeEnd(m.sourceName, m.sourceRtId, m.sourceAttributePath);
16017
+ const target = describeEnd(m.targetName, m.targetRtId, m.targetAttributePath);
16018
+ if (!source && !target)
16019
+ return;
16020
+ const generated = `${source || '(no source)'} → ${target || '(no target)'}`;
16021
+ this.model.update(curr => ({ ...curr, name: generated }));
16022
+ }
16023
+ isValid() {
16024
+ const m = this.model();
16025
+ // Both ends must be linked and the target attribute path filled — a
16026
+ // mapping without these can't actually fire on the runtime side.
16027
+ if (!m.sourceRtId || !m.sourceCkTypeId)
16028
+ return false;
16029
+ if (!m.targetRtId || !m.targetCkTypeId)
16030
+ return false;
16031
+ return !!m.targetAttributePath && m.targetAttributePath.trim().length > 0;
16032
+ }
16033
+ async pickSource() {
16034
+ const current = this.model();
16035
+ const result = await this.entitySelector.openEntitySelector({
16036
+ title: 'Select Source Entity',
16037
+ currentTargetRtId: current.sourceRtId,
16038
+ currentTargetCkTypeId: current.sourceCkTypeId,
16039
+ });
16040
+ if (!result.confirmed || !result.entity)
16041
+ return;
16042
+ const changedType = result.entity.ckTypeId !== current.sourceCkTypeId;
16043
+ this.model.update(m => ({
16044
+ ...m,
16045
+ sourceRtId: result.entity.rtId,
16046
+ sourceCkTypeId: result.entity.ckTypeId,
16047
+ sourceName: result.entity.name ?? result.entity.rtId,
16048
+ // Reset the path when the type changes — the previous attribute likely
16049
+ // doesn't exist on the new type.
16050
+ sourceAttributePath: changedType ? '' : m.sourceAttributePath,
16051
+ }));
16052
+ // The data-point picker watches sourceCkTypeId/sourceRtId and refreshes
16053
+ // its option list on its own — no extra wiring needed here.
16054
+ }
16055
+ async pickTarget() {
16056
+ const current = this.model();
16057
+ const result = await this.entitySelector.openEntitySelector({
16058
+ title: 'Select Target Entity',
16059
+ currentTargetRtId: current.targetRtId,
16060
+ currentTargetCkTypeId: current.targetCkTypeId,
16061
+ });
16062
+ if (!result.confirmed || !result.entity)
16063
+ return;
16064
+ const changedType = result.entity.ckTypeId !== current.targetCkTypeId;
16065
+ this.model.update(m => ({
16066
+ ...m,
16067
+ targetRtId: result.entity.rtId,
16068
+ targetCkTypeId: result.entity.ckTypeId,
16069
+ targetName: result.entity.name ?? result.entity.rtId,
16070
+ targetAttributePath: changedType ? '' : m.targetAttributePath,
16071
+ }));
16072
+ if (changedType) {
16073
+ this.targetFilter.set('');
16074
+ void this.loadTargetAttributes(result.entity.ckTypeId);
16075
+ }
16076
+ }
16077
+ onSourceAttributePathChange(value) {
16078
+ this.model.update(m => ({ ...m, sourceAttributePath: value }));
16079
+ }
16080
+ onTargetAttributePathChange(value) {
16081
+ this.model.update(m => ({ ...m, targetAttributePath: value ?? '' }));
16082
+ }
16083
+ onTargetAttributeFilter(filter) {
16084
+ this.targetFilter.set(filter);
16085
+ }
16086
+ onSave() {
16087
+ if (!this.isValid())
16088
+ return;
16089
+ const result = { confirmed: true, mapping: this.model() };
16090
+ this.windowRef.close(result);
16091
+ }
16092
+ onCancel() {
16093
+ const result = { confirmed: false };
16094
+ this.windowRef.close(result);
16095
+ }
16096
+ /**
16097
+ * Fetches the CK-schema attribute catalogue for the target side and stores
16098
+ * it for the target combobox dropdown. Navigation properties are excluded —
16099
+ * they drown out the direct attributes; the browse button below opens the
16100
+ * full {@link AttributeSelectorDialog} for users who need a deep path.
16101
+ *
16102
+ * (The source side now uses {@link DataPointPickerComponent}, which queries
16103
+ * the runtime entity's States RecordArray directly — runtime data points,
16104
+ * not CK schema attributes, are what the runtime engine matches against.)
16105
+ */
16106
+ async loadTargetAttributes(ckTypeId) {
16107
+ this.targetAttributesLoading.set(true);
16108
+ try {
16109
+ const result = await firstValueFrom(this.attributeService.getAvailableAttributes(ckTypeId, undefined, // filter
16110
+ 500, // first
16111
+ undefined, // after
16112
+ undefined, // attributeValueType
16113
+ undefined, // searchTerm
16114
+ false, // includeNavigationProperties — keep the list focused
16115
+ 0));
16116
+ this.targetAttributes.set(result.items);
16117
+ }
16118
+ catch (err) {
16119
+ console.error(`Failed to load target attributes for ${ckTypeId}:`, err);
16120
+ this.targetAttributes.set([]);
16121
+ }
16122
+ finally {
16123
+ this.targetAttributesLoading.set(false);
16124
+ }
16125
+ }
16126
+ async browseTargetAttribute() {
16127
+ const ckTypeId = this.effectiveTargetCkTypeId();
16128
+ if (!ckTypeId)
16129
+ return;
16130
+ const current = this.model().targetAttributePath;
16131
+ const result = await this.attributeSelectorDialog.openAttributeSelector(ckTypeId, current ? [current] : undefined, 'Select Target Attribute Path', true);
16132
+ if (result.confirmed && result.selectedAttributes.length > 0) {
16133
+ this.model.update(m => ({
16134
+ ...m,
16135
+ targetAttributePath: result.selectedAttributes[0].attributePath,
16136
+ }));
16137
+ }
16138
+ }
16139
+ emptyValue() {
16140
+ return {
16141
+ rtId: '',
16142
+ ckTypeId: '',
16143
+ name: '',
16144
+ enabled: true,
16145
+ sourceAttributePath: '',
16146
+ mappingExpression: '',
16147
+ targetAttributePath: '',
16148
+ };
16149
+ }
16150
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingEditDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16151
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MappingEditDialogComponent, isStandalone: true, selector: "mm-mapping-edit-dialog", ngImport: i0, template: `
16152
+ <div class="mapping-edit">
16153
+ <div class="mapping-edit-body">
16154
+ <div class="field-row">
16155
+ <label>Name</label>
16156
+ <div class="combo-row">
16157
+ <kendo-textbox [(value)]="model().name"
16158
+ placeholder="Mapping name">
16159
+ </kendo-textbox>
16160
+ <button kendoButton fillMode="flat" size="small"
16161
+ [svgIcon]="icons.regenerate"
16162
+ [disabled]="!canGenerateName()"
16163
+ (click)="generateName()"
16164
+ title="Generate name from source + target"></button>
16165
+ </div>
16166
+ </div>
16167
+
16168
+ <div class="field-row inline">
16169
+ <label>Enabled</label>
16170
+ <kendo-switch [(ngModel)]="model().enabled" size="small"></kendo-switch>
16171
+ @if (!model().enabled) {
16172
+ <span class="hint">Mapping is disabled — it will be skipped by data acquisition.</span>
16173
+ }
16174
+ </div>
16175
+
16176
+ <div class="field-row">
16177
+ <label>Source Entity</label>
16178
+ @if (model().sourceRtId) {
16179
+ <div class="entity-display">
16180
+ <div class="entity-main">
16181
+ <span class="entity-name">{{ model().sourceName || model().sourceRtId }}</span>
16182
+ <button kendoButton fillMode="flat" size="small"
16183
+ (click)="pickSource()">Change…</button>
16184
+ </div>
16185
+ <div class="entity-meta">
16186
+ <span>{{ model().sourceCkTypeId }}</span>
16187
+ <span class="sep">@</span>
16188
+ <span>{{ model().sourceRtId }}</span>
16189
+ </div>
16190
+ </div>
16191
+ } @else {
16192
+ <div class="entity-display empty">
16193
+ <span class="hint">No source entity linked.</span>
16194
+ <button kendoButton fillMode="flat" size="small"
16195
+ (click)="pickSource()">Select…</button>
16196
+ </div>
16197
+ }
16198
+ </div>
16199
+
16200
+ <div class="field-row">
16201
+ <label>Source Data Point</label>
16202
+ <mm-data-point-picker
16203
+ [entityRtId]="model().sourceRtId"
16204
+ [entityCkTypeId]="model().sourceCkTypeId"
16205
+ [value]="model().sourceAttributePath"
16206
+ [disabled]="!model().sourceRtId || !model().sourceCkTypeId"
16207
+ (valueChange)="onSourceAttributePathChange($event)">
16208
+ </mm-data-point-picker>
16209
+ <span class="hint">
16210
+ @if (model().sourceRtId && model().sourceCkTypeId) {
16211
+ Data points exposed by the source entity's
16212
+ <code>States</code> / <code>DataPoints</code> array, with
16213
+ <code>currentValue</code> as the default for single-state controls.
16214
+ Free text allowed.
16215
+ } @else {
16216
+ Pick a source entity above to load its available data points.
16217
+ }
16218
+ </span>
16219
+ </div>
16220
+
16221
+ <div class="field-row">
16222
+ <label>Mapping Expression</label>
16223
+ <kendo-textbox [(value)]="model().mappingExpression"
16224
+ placeholder="e.g. value, value / 100, value > 50 ? 1 : 0">
16225
+ </kendo-textbox>
16226
+ <span class="hint">Optional. mXparser expression. <code>value</code> = raw source value.</span>
16227
+ </div>
16228
+
16229
+ <div class="field-row">
16230
+ <label>Target Entity</label>
16231
+ @if (model().targetRtId) {
16232
+ <div class="entity-display">
16233
+ <div class="entity-main">
16234
+ <span class="entity-name">{{ model().targetName || model().targetRtId }}</span>
16235
+ <button kendoButton fillMode="flat" size="small"
16236
+ (click)="pickTarget()">Change…</button>
16237
+ </div>
16238
+ <div class="entity-meta">
16239
+ <span>{{ model().targetCkTypeId }}</span>
16240
+ <span class="sep">&#64;</span>
16241
+ <span>{{ model().targetRtId }}</span>
16242
+ </div>
16243
+ </div>
16244
+ } @else {
16245
+ <div class="entity-display empty">
16246
+ <span class="hint">No target entity linked.</span>
16247
+ <button kendoButton fillMode="flat" size="small"
16248
+ (click)="pickTarget()">Select…</button>
16249
+ </div>
16250
+ }
16251
+ </div>
16252
+
16253
+ <div class="field-row">
16254
+ <label>
16255
+ Target Attribute Path
16256
+ @if (targetAttributesLoading()) { <span class="loading-pill">loading…</span> }
16257
+ </label>
16258
+ <div class="combo-row">
16259
+ <kendo-combobox
16260
+ [data]="targetAttributeList()"
16261
+ [value]="model().targetAttributePath"
16262
+ (valueChange)="onTargetAttributePathChange($event)"
16263
+ [textField]="'attributePath'"
16264
+ [valueField]="'attributePath'"
16265
+ [valuePrimitive]="true"
16266
+ [allowCustom]="true"
16267
+ [filterable]="true"
16268
+ (filterChange)="onTargetAttributeFilter($event)"
16269
+ [popupSettings]="{ appendTo: 'root', animate: true }"
16270
+ placeholder="e.g. Temperature, CO2Level">
16271
+ <ng-template kendoComboBoxItemTemplate let-item>
16272
+ <div class="attribute-option">
16273
+ <span class="attribute-path">{{ item.attributePath }}</span>
16274
+ <span class="attribute-type">{{ item.attributeValueType }}</span>
16275
+ </div>
16276
+ </ng-template>
16277
+ </kendo-combobox>
16278
+ <button kendoButton fillMode="flat" size="small"
16279
+ [svgIcon]="icons.browse"
16280
+ [disabled]="!effectiveTargetCkTypeId()"
16281
+ (click)="browseTargetAttribute()"
16282
+ title="Browse all attributes (incl. navigation properties)"></button>
16283
+ </div>
16284
+ <span class="hint">
16285
+ @if (effectiveTargetCkTypeId(); as t) {
16286
+ Direct attributes on <code>{{ t }}</code>. Click the browse button
16287
+ for navigation properties or to escape autocomplete.
16288
+ } @else {
16289
+ Pick a target entity above to see available attribute paths.
16290
+ }
16291
+ </span>
16292
+ </div>
16293
+
16294
+ </div>
16295
+ <div class="dialog-actions">
16296
+ <button kendoButton (click)="onCancel()">Cancel</button>
16297
+ <button kendoButton themeColor="primary" (click)="onSave()"
16298
+ [disabled]="!isValid()">Save</button>
16299
+ </div>
16300
+ </div>
16301
+ `, isInline: true, styles: [".mapping-edit{display:flex;flex-direction:column;height:100%;box-sizing:border-box}.mapping-edit-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 18px;display:flex;flex-direction:column;gap:14px}.field-row{display:flex;flex-direction:column;gap:4px}.field-row label{font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.field-row .hint{font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-style:italic}.field-row.inline{flex-direction:row;align-items:center;gap:8px}.entity-display{display:flex;flex-direction:column;gap:2px;padding:6px 10px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:4px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.entity-display.empty{flex-direction:row;align-items:center;justify-content:space-between}.entity-main{display:flex;align-items:center;gap:8px}.entity-main .entity-name{flex:1;font-weight:600;font-size:.9rem}.entity-meta{display:flex;align-items:center;gap:2px;font-size:.7rem;font-family:monospace;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.entity-meta .sep{opacity:.7}.dialog-actions{flex:0 0 auto;display:flex;gap:8px;justify-content:flex-end;padding:10px 18px;border-top:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 70%,transparent)}.combo-row{display:flex;align-items:stretch;gap:4px}.combo-row kendo-combobox{flex:1}.attribute-option{display:flex;justify-content:space-between;align-items:center;width:100%;gap:8px}.attribute-option .attribute-path{flex:1;font-family:monospace;font-size:.85rem}.attribute-option .attribute-type{font-size:.7rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f0f0f0)) 80%,transparent);color:var(--theme-text-secondary, var(--kendo-color-subtle, #888))}.loading-pill{margin-left:6px;font-size:.65rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);color:var(--kendo-color-info, #0dcaf0);text-transform:none;letter-spacing:0;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: ComboBoxModule }, { kind: "component", type: i3$1.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "directive", type: i3$1.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "ngmodule", type: SVGIconModule }, { kind: "ngmodule", type: SwitchModule }, { kind: "component", type: i5.SwitchComponent, selector: "kendo-switch", inputs: ["focusableId", "onLabel", "offLabel", "checked", "disabled", "readonly", "tabindex", "size", "thumbRounded", "trackRounded", "tabIndex"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoSwitch"] }, { kind: "ngmodule", type: TextBoxModule }, { kind: "component", type: i5.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: DataPointPickerComponent, selector: "mm-data-point-picker", inputs: ["entity", "entityRtId", "entityCkTypeId", "value", "placeholder", "disabled"], outputs: ["valueChange", "filterChange"] }] });
16302
+ }
16303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingEditDialogComponent, decorators: [{
16304
+ type: Component,
16305
+ args: [{ selector: 'mm-mapping-edit-dialog', standalone: true, imports: [
16306
+ CommonModule,
16307
+ FormsModule,
16308
+ ButtonModule,
16309
+ ComboBoxModule,
16310
+ SVGIconModule,
16311
+ SwitchModule,
16312
+ TextBoxModule,
16313
+ DataPointPickerComponent,
16314
+ ], template: `
16315
+ <div class="mapping-edit">
16316
+ <div class="mapping-edit-body">
16317
+ <div class="field-row">
16318
+ <label>Name</label>
16319
+ <div class="combo-row">
16320
+ <kendo-textbox [(value)]="model().name"
16321
+ placeholder="Mapping name">
16322
+ </kendo-textbox>
16323
+ <button kendoButton fillMode="flat" size="small"
16324
+ [svgIcon]="icons.regenerate"
16325
+ [disabled]="!canGenerateName()"
16326
+ (click)="generateName()"
16327
+ title="Generate name from source + target"></button>
16328
+ </div>
16329
+ </div>
16330
+
16331
+ <div class="field-row inline">
16332
+ <label>Enabled</label>
16333
+ <kendo-switch [(ngModel)]="model().enabled" size="small"></kendo-switch>
16334
+ @if (!model().enabled) {
16335
+ <span class="hint">Mapping is disabled — it will be skipped by data acquisition.</span>
16336
+ }
16337
+ </div>
16338
+
16339
+ <div class="field-row">
16340
+ <label>Source Entity</label>
16341
+ @if (model().sourceRtId) {
16342
+ <div class="entity-display">
16343
+ <div class="entity-main">
16344
+ <span class="entity-name">{{ model().sourceName || model().sourceRtId }}</span>
16345
+ <button kendoButton fillMode="flat" size="small"
16346
+ (click)="pickSource()">Change…</button>
16347
+ </div>
16348
+ <div class="entity-meta">
16349
+ <span>{{ model().sourceCkTypeId }}</span>
16350
+ <span class="sep">@</span>
16351
+ <span>{{ model().sourceRtId }}</span>
16352
+ </div>
16353
+ </div>
16354
+ } @else {
16355
+ <div class="entity-display empty">
16356
+ <span class="hint">No source entity linked.</span>
16357
+ <button kendoButton fillMode="flat" size="small"
16358
+ (click)="pickSource()">Select…</button>
16359
+ </div>
16360
+ }
16361
+ </div>
16362
+
16363
+ <div class="field-row">
16364
+ <label>Source Data Point</label>
16365
+ <mm-data-point-picker
16366
+ [entityRtId]="model().sourceRtId"
16367
+ [entityCkTypeId]="model().sourceCkTypeId"
16368
+ [value]="model().sourceAttributePath"
16369
+ [disabled]="!model().sourceRtId || !model().sourceCkTypeId"
16370
+ (valueChange)="onSourceAttributePathChange($event)">
16371
+ </mm-data-point-picker>
16372
+ <span class="hint">
16373
+ @if (model().sourceRtId && model().sourceCkTypeId) {
16374
+ Data points exposed by the source entity's
16375
+ <code>States</code> / <code>DataPoints</code> array, with
16376
+ <code>currentValue</code> as the default for single-state controls.
16377
+ Free text allowed.
16378
+ } @else {
16379
+ Pick a source entity above to load its available data points.
16380
+ }
16381
+ </span>
16382
+ </div>
16383
+
16384
+ <div class="field-row">
16385
+ <label>Mapping Expression</label>
16386
+ <kendo-textbox [(value)]="model().mappingExpression"
16387
+ placeholder="e.g. value, value / 100, value > 50 ? 1 : 0">
16388
+ </kendo-textbox>
16389
+ <span class="hint">Optional. mXparser expression. <code>value</code> = raw source value.</span>
16390
+ </div>
16391
+
16392
+ <div class="field-row">
16393
+ <label>Target Entity</label>
16394
+ @if (model().targetRtId) {
16395
+ <div class="entity-display">
16396
+ <div class="entity-main">
16397
+ <span class="entity-name">{{ model().targetName || model().targetRtId }}</span>
16398
+ <button kendoButton fillMode="flat" size="small"
16399
+ (click)="pickTarget()">Change…</button>
16400
+ </div>
16401
+ <div class="entity-meta">
16402
+ <span>{{ model().targetCkTypeId }}</span>
16403
+ <span class="sep">&#64;</span>
16404
+ <span>{{ model().targetRtId }}</span>
16405
+ </div>
16406
+ </div>
16407
+ } @else {
16408
+ <div class="entity-display empty">
16409
+ <span class="hint">No target entity linked.</span>
16410
+ <button kendoButton fillMode="flat" size="small"
16411
+ (click)="pickTarget()">Select…</button>
16412
+ </div>
16413
+ }
16414
+ </div>
16415
+
16416
+ <div class="field-row">
16417
+ <label>
16418
+ Target Attribute Path
16419
+ @if (targetAttributesLoading()) { <span class="loading-pill">loading…</span> }
16420
+ </label>
16421
+ <div class="combo-row">
16422
+ <kendo-combobox
16423
+ [data]="targetAttributeList()"
16424
+ [value]="model().targetAttributePath"
16425
+ (valueChange)="onTargetAttributePathChange($event)"
16426
+ [textField]="'attributePath'"
16427
+ [valueField]="'attributePath'"
16428
+ [valuePrimitive]="true"
16429
+ [allowCustom]="true"
16430
+ [filterable]="true"
16431
+ (filterChange)="onTargetAttributeFilter($event)"
16432
+ [popupSettings]="{ appendTo: 'root', animate: true }"
16433
+ placeholder="e.g. Temperature, CO2Level">
16434
+ <ng-template kendoComboBoxItemTemplate let-item>
16435
+ <div class="attribute-option">
16436
+ <span class="attribute-path">{{ item.attributePath }}</span>
16437
+ <span class="attribute-type">{{ item.attributeValueType }}</span>
16438
+ </div>
16439
+ </ng-template>
16440
+ </kendo-combobox>
16441
+ <button kendoButton fillMode="flat" size="small"
16442
+ [svgIcon]="icons.browse"
16443
+ [disabled]="!effectiveTargetCkTypeId()"
16444
+ (click)="browseTargetAttribute()"
16445
+ title="Browse all attributes (incl. navigation properties)"></button>
16446
+ </div>
16447
+ <span class="hint">
16448
+ @if (effectiveTargetCkTypeId(); as t) {
16449
+ Direct attributes on <code>{{ t }}</code>. Click the browse button
16450
+ for navigation properties or to escape autocomplete.
16451
+ } @else {
16452
+ Pick a target entity above to see available attribute paths.
16453
+ }
16454
+ </span>
16455
+ </div>
16456
+
16457
+ </div>
16458
+ <div class="dialog-actions">
16459
+ <button kendoButton (click)="onCancel()">Cancel</button>
16460
+ <button kendoButton themeColor="primary" (click)="onSave()"
16461
+ [disabled]="!isValid()">Save</button>
16462
+ </div>
16463
+ </div>
16464
+ `, styles: [".mapping-edit{display:flex;flex-direction:column;height:100%;box-sizing:border-box}.mapping-edit-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 18px;display:flex;flex-direction:column;gap:14px}.field-row{display:flex;flex-direction:column;gap:4px}.field-row label{font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.field-row .hint{font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-style:italic}.field-row.inline{flex-direction:row;align-items:center;gap:8px}.entity-display{display:flex;flex-direction:column;gap:2px;padding:6px 10px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:4px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.entity-display.empty{flex-direction:row;align-items:center;justify-content:space-between}.entity-main{display:flex;align-items:center;gap:8px}.entity-main .entity-name{flex:1;font-weight:600;font-size:.9rem}.entity-meta{display:flex;align-items:center;gap:2px;font-size:.7rem;font-family:monospace;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.entity-meta .sep{opacity:.7}.dialog-actions{flex:0 0 auto;display:flex;gap:8px;justify-content:flex-end;padding:10px 18px;border-top:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 70%,transparent)}.combo-row{display:flex;align-items:stretch;gap:4px}.combo-row kendo-combobox{flex:1}.attribute-option{display:flex;justify-content:space-between;align-items:center;width:100%;gap:8px}.attribute-option .attribute-path{flex:1;font-family:monospace;font-size:.85rem}.attribute-option .attribute-type{font-size:.7rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f0f0f0)) 80%,transparent);color:var(--theme-text-secondary, var(--kendo-color-subtle, #888))}.loading-pill{margin-left:6px;font-size:.65rem;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);color:var(--kendo-color-info, #0dcaf0);text-transform:none;letter-spacing:0;font-style:italic}\n"] }]
16465
+ }] });
16466
+ /**
16467
+ * Client-side filter mirroring the substring search Kendo's combobox does
16468
+ * internally. Centralised here so the dropdown still shows a reasonable list
16469
+ * when the user types a path that hasn't been seen on this CK type yet.
16470
+ */
16471
+ function filterAttributes(all, filter) {
16472
+ if (!filter || filter.trim().length === 0)
16473
+ return all;
16474
+ const needle = filter.toLowerCase();
16475
+ return all.filter(a => a.attributePath.toLowerCase().includes(needle));
16476
+ }
16477
+ /**
16478
+ * Produces the "{label} {path}" fragment used on either side of the generated
16479
+ * mapping name. Uses the entity name when present; otherwise a shortened rtId
16480
+ * suffix (last 6 hex chars) keeps the name compact while staying identifiable.
16481
+ * Returns an empty string when nothing useful is available.
16482
+ */
16483
+ function describeEnd(entityName, rtId, attributePath) {
16484
+ const label = entityName?.trim() || (rtId ? `…${rtId.slice(-6)}` : '');
16485
+ const path = attributePath?.trim();
16486
+ if (label && path)
16487
+ return `${label} ${path}`;
16488
+ return label || path || '';
16489
+ }
16490
+
16491
+ const DIALOG_KEY = 'mapping-edit-dialog';
16492
+ const DEFAULT_SIZE = { width: 760, height: 760 };
16493
+ class MappingEditDialogService {
16494
+ windowService = inject(WindowService);
16495
+ windowStateService = inject(WindowStateService);
16496
+ /**
16497
+ * Opens the focused mapping editor as a resizable Kendo Window and returns
16498
+ * the user's choice. The window's size is persisted across sessions via
16499
+ * `WindowStateService` (same pattern as the AttributeSelectorDialog and the
16500
+ * EntitySelectDialog).
16501
+ *
16502
+ * The host is responsible for persisting changes when `confirmed=true` — the
16503
+ * dialog itself only mutates a local copy.
16504
+ */
16505
+ async open(data) {
16506
+ const size = this.windowStateService.resolveWindowSize(DIALOG_KEY, DEFAULT_SIZE);
16507
+ const windowRef = this.windowService.open({
16508
+ title: data.title ?? 'Edit Mapping',
16509
+ content: MappingEditDialogComponent,
16510
+ width: size.width,
16511
+ height: size.height,
16512
+ minWidth: 540,
16513
+ minHeight: 480,
16514
+ resizable: true,
16515
+ });
16516
+ this.windowStateService.applyModalBehavior(DIALOG_KEY, windowRef);
16517
+ const contentRef = windowRef.content;
16518
+ if (contentRef?.instance) {
16519
+ contentRef.instance.initialise(data);
16520
+ }
16521
+ try {
16522
+ const result = await firstValueFrom(windowRef.result);
16523
+ if (result instanceof WindowCloseResult) {
16524
+ return { confirmed: false };
16525
+ }
16526
+ if (result && typeof result === 'object' && 'confirmed' in result) {
16527
+ return result;
16528
+ }
16529
+ return { confirmed: false };
16530
+ }
16531
+ catch {
16532
+ return { confirmed: false };
16533
+ }
16534
+ }
16535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingEditDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
16536
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingEditDialogService, providedIn: 'root' });
16537
+ }
16538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingEditDialogService, decorators: [{
16539
+ type: Injectable,
16540
+ args: [{ providedIn: 'root' }]
16541
+ }] });
16542
+
16543
+ /**
16544
+ * Default configuration: Basic/Tree + Basic/TreeNode driven by System/ParentChild,
16545
+ * mappings via System.Communication/MapsTo.
16546
+ */
16547
+ const DEFAULT_MAPPING_COVERAGE_TREE_CONFIG = {
16548
+ rootCkTypeId: 'Basic/Tree',
16549
+ childCkTypeId: 'Basic/TreeNode',
16550
+ childRoleId: 'System/ParentChild',
16551
+ mappingRoleId: 'System.Communication/MapsTo',
16552
+ mappingSourceRoleId: 'System.Communication/MapsFrom',
16553
+ mappingCkTypeId: 'System.Communication/DataPointMapping',
16554
+ mappingTargetOutboundRoleName: 'mappedAsTarget',
16555
+ mappingSourceOutboundRoleName: 'mappedAsSource',
16556
+ validationPipelineCkTypeId: 'System.Communication/Pipeline',
16557
+ validationExecutionCkTypeId: 'System.Communication/PipelineExecution',
16558
+ validationExecutesRoleId: 'System.Communication/ExecutedPipeline',
16559
+ sourceCandidateCkTypeIds: [],
16560
+ };
16561
+
16562
+ /**
16563
+ * Master-detail component that visualises mapping coverage on a generic entity
16564
+ * hierarchy (defaults: Basic/Tree + Basic/TreeNode, mappings via
16565
+ * System.Communication/MapsTo). The user picks a root, browses the hierarchy on
16566
+ * the left and inspects / edits DataPointMappings on the right.
16567
+ *
16568
+ * Phase 1: tree + counts + read-only mapping list + CRUD (add new mapping,
16569
+ * relink source via {@link EntitySelectorDialogService}, delete).
16570
+ */
16571
+ class MappingCoverageTreeComponent {
16572
+ entitySelector = inject(EntitySelectorDialogService);
16573
+ editDialog = inject(MappingEditDialogService);
16574
+ confirmation = inject(ConfirmationService);
16575
+ communicationService = inject(CommunicationService);
16576
+ getEntitiesByCkType = inject(GetEntitiesByCkTypeDtoGQL);
16577
+ getNodeMappingsGQL = inject(GetNodeMappingsDtoGQL);
16578
+ getRuntimeEntityByIdGQL = inject(GetRuntimeEntityByIdDtoGQL);
16579
+ getLatestValidationGQL = inject(GetLatestValidationExecutionDtoGQL);
16580
+ getOrphanCandidatesGQL = inject(GetOrphanCandidatesDtoGQL);
16581
+ createEntitiesGQL = inject(CreateEntitiesDtoGQL);
16582
+ deleteEntitiesGQL = inject(DeleteEntitiesDtoGQL);
16583
+ updateEntitiesGQL = inject(UpdateRuntimeEntitiesDtoGQL);
16584
+ dataSource = inject(MappingCoverageTreeDataSource);
16585
+ treeView;
16586
+ /** Optional override for non-default hierarchies / mapping roles. */
16587
+ config = DEFAULT_MAPPING_COVERAGE_TREE_CONFIG;
16588
+ /** Pre-select a root on first show (e.g. via route param). */
16589
+ initialRoot = null;
16590
+ /**
16591
+ * Current tenant ID. Required for the "Run Validation" pipeline trigger
16592
+ * which calls a tenant-scoped REST endpoint on the Communication Controller.
16593
+ * When not provided, the Run button is hidden and the user has to trigger
16594
+ * the pipeline externally.
16595
+ */
16596
+ tenantId = null;
16597
+ entitySelected = new EventEmitter();
16598
+ icons = {
16599
+ refresh: arrowRotateCwIcon,
16600
+ folderOpen: folderOpenIcon,
16601
+ plus: plusIcon,
16602
+ pencil: pencilIcon,
16603
+ trash: trashIcon,
16604
+ link: linkIcon,
16605
+ };
16606
+ rootCandidates = signal([], ...(ngDevMode ? [{ debugName: "rootCandidates" }] : /* istanbul ignore next */ []));
16607
+ selectedRoot = signal(null, ...(ngDevMode ? [{ debugName: "selectedRoot" }] : /* istanbul ignore next */ []));
16608
+ selectedNode = signal(null, ...(ngDevMode ? [{ debugName: "selectedNode" }] : /* istanbul ignore next */ []));
16609
+ mappings = signal([], ...(ngDevMode ? [{ debugName: "mappings" }] : /* istanbul ignore next */ []));
16610
+ mappingsLoading = signal(false, ...(ngDevMode ? [{ debugName: "mappingsLoading" }] : /* istanbul ignore next */ []));
16611
+ mappingsError = signal(null, ...(ngDevMode ? [{ debugName: "mappingsError" }] : /* istanbul ignore next */ []));
16612
+ validationPipelines = signal([], ...(ngDevMode ? [{ debugName: "validationPipelines" }] : /* istanbul ignore next */ []));
16613
+ selectedPipeline = signal(null, ...(ngDevMode ? [{ debugName: "selectedPipeline" }] : /* istanbul ignore next */ []));
16614
+ validationSummary = signal(null, ...(ngDevMode ? [{ debugName: "validationSummary" }] : /* istanbul ignore next */ []));
16615
+ validationExecutedAt = signal(null, ...(ngDevMode ? [{ debugName: "validationExecutedAt" }] : /* istanbul ignore next */ []));
16616
+ validationLoading = signal(false, ...(ngDevMode ? [{ debugName: "validationLoading" }] : /* istanbul ignore next */ []));
16617
+ validationError = signal(null, ...(ngDevMode ? [{ debugName: "validationError" }] : /* istanbul ignore next */ []));
16618
+ validationRunning = signal(false, ...(ngDevMode ? [{ debugName: "validationRunning" }] : /* istanbul ignore next */ []));
16619
+ /** Active tab: 'coverage' shows the tree, 'orphans' shows the unmapped sources. */
16620
+ activeTab = signal('coverage', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
16621
+ /** Source CK type currently inspected for orphans. */
16622
+ orphanCkType = signal(null, ...(ngDevMode ? [{ debugName: "orphanCkType" }] : /* istanbul ignore next */ []));
16623
+ orphanCandidates = signal([], ...(ngDevMode ? [{ debugName: "orphanCandidates" }] : /* istanbul ignore next */ []));
16624
+ orphanLoading = signal(false, ...(ngDevMode ? [{ debugName: "orphanLoading" }] : /* istanbul ignore next */ []));
16625
+ orphanError = signal(null, ...(ngDevMode ? [{ debugName: "orphanError" }] : /* istanbul ignore next */ []));
16626
+ orphanHideMapped = signal(true, ...(ngDevMode ? [{ debugName: "orphanHideMapped" }] : /* istanbul ignore next */ []));
16627
+ orphanFilteredList = computed(() => {
16628
+ const all = this.orphanCandidates();
16629
+ return this.orphanHideMapped() ? all.filter(c => c.mappingCount === 0) : all;
16630
+ }, ...(ngDevMode ? [{ debugName: "orphanFilteredList" }] : /* istanbul ignore next */ []));
16631
+ orphanStats = computed(() => {
16632
+ const all = this.orphanCandidates();
16633
+ const unmapped = all.filter(c => c.mappingCount === 0).length;
16634
+ return { total: all.length, unmapped, mapped: all.length - unmapped };
16635
+ }, ...(ngDevMode ? [{ debugName: "orphanStats" }] : /* istanbul ignore next */ []));
16636
+ /**
16637
+ * Which parent CK type to group by, or null for a flat list. We let the user
16638
+ * pick the type instead of just "immediate parent" because Loxone-style trees
16639
+ * include intermediate buckets (Loxone/Category) where each parent rtId is
16640
+ * unique per room — grouping by Category produces dozens of look-alike
16641
+ * sections ("Stellantrieb" appears N times, once per room). The user almost
16642
+ * always wants Loxone/Room or whichever level genuinely partitions the data,
16643
+ * so we expose all parent types seen in the loaded data and let them choose.
16644
+ */
16645
+ orphanGroupParentType = signal(null, ...(ngDevMode ? [{ debugName: "orphanGroupParentType" }] : /* istanbul ignore next */ []));
16646
+ /**
16647
+ * Distinct parent CK type ids found in the loaded candidates, sorted so the
16648
+ * deepest type (root-most ancestor) comes first. For Loxone-Controls this is
16649
+ * Loxone/Room first, then Loxone/Category — usually the deeper one is also
16650
+ * the more meaningful grouping context.
16651
+ */
16652
+ orphanAvailableParentTypes = computed(() => {
16653
+ const maxDepthByType = new Map();
16654
+ for (const item of this.orphanCandidates()) {
16655
+ item.parentPath.forEach((p, idx) => {
16656
+ const prev = maxDepthByType.get(p.ckTypeId) ?? -1;
16657
+ if (idx > prev)
16658
+ maxDepthByType.set(p.ckTypeId, idx);
16659
+ });
16660
+ }
16661
+ return Array.from(maxDepthByType.entries())
16662
+ .sort((a, b) => b[1] - a[1])
16663
+ .map(([ckTypeId]) => ckTypeId);
16664
+ }, ...(ngDevMode ? [{ debugName: "orphanAvailableParentTypes" }] : /* istanbul ignore next */ []));
16665
+ orphanGroupedList = computed(() => {
16666
+ const groupBy = this.orphanGroupParentType();
16667
+ if (!groupBy)
16668
+ return [];
16669
+ const groups = new Map();
16670
+ for (const item of this.orphanFilteredList()) {
16671
+ // First ancestor of the chosen type (closest to leaf wins). Falls back to
16672
+ // the catch-all bucket when no ancestor of that type is in the chain.
16673
+ const ancestor = item.parentPath.find(p => p.ckTypeId === groupBy);
16674
+ const key = ancestor ? `${ancestor.ckTypeId}@${ancestor.rtId}` : '__no_parent__';
16675
+ const label = ancestor?.name ?? '(no parent of this type)';
16676
+ let group = groups.get(key);
16677
+ if (!group) {
16678
+ group = { key, label, items: [] };
16679
+ groups.set(key, group);
16680
+ }
16681
+ group.items.push(item);
16682
+ }
16683
+ // Sort groups by their (locale-aware) label; "(no parent…)" lands last.
16684
+ return Array.from(groups.values()).sort((a, b) => {
16685
+ if (a.key === '__no_parent__')
16686
+ return 1;
16687
+ if (b.key === '__no_parent__')
16688
+ return -1;
16689
+ return a.label.localeCompare(b.label);
16690
+ });
16691
+ }, ...(ngDevMode ? [{ debugName: "orphanGroupedList" }] : /* istanbul ignore next */ []));
16692
+ summaryLine = computed(() => {
16693
+ const node = this.selectedNode();
16694
+ if (!node)
16695
+ return null;
16696
+ const items = this.mappings();
16697
+ const enabled = items.filter(m => m.enabled).length;
16698
+ return `${items.length} mapping(s), ${enabled} enabled`;
16699
+ }, ...(ngDevMode ? [{ debugName: "summaryLine" }] : /* istanbul ignore next */ []));
16700
+ async ngOnInit() {
16701
+ this.dataSource.setConfig(this.config);
16702
+ if (this.config.sourceCandidateCkTypeIds.length > 0) {
16703
+ this.orphanCkType.set(this.config.sourceCandidateCkTypeIds[0]);
16704
+ }
16705
+ await Promise.all([this.loadRootCandidates(), this.loadValidationPipelines()]);
16706
+ if (this.initialRoot) {
16707
+ const match = this.rootCandidates().find(r => r.rtId === this.initialRoot?.rtId);
16708
+ if (match) {
16709
+ await this.selectRoot(match);
16710
+ }
16711
+ }
16712
+ }
16713
+ onPipelineSelectChange(rtId) {
16714
+ const match = this.validationPipelines().find(p => p.rtId === rtId);
16715
+ this.selectedPipeline.set(match ?? null);
16716
+ }
16717
+ async refreshValidation() {
16718
+ const pipeline = this.selectedPipeline();
16719
+ if (!pipeline) {
16720
+ this.validationError.set('Pick a validation pipeline first.');
16721
+ return;
16722
+ }
16723
+ this.validationLoading.set(true);
16724
+ this.validationError.set(null);
16725
+ try {
16726
+ const data = await firstValueFrom(this.getLatestValidationGQL
16727
+ .fetch({
16728
+ variables: {
16729
+ pipelineRtId: pipeline.rtId,
16730
+ pipelineCkTypeId: pipeline.ckTypeId,
16731
+ executesRoleId: this.config.validationExecutesRoleId,
16732
+ executionCkTypeId: this.config.validationExecutionCkTypeId,
16733
+ },
16734
+ fetchPolicy: 'network-only',
16735
+ })
16736
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items?.[0])));
16737
+ const execution = data?.associations?.executions?.items?.[0];
16738
+ const attrs = execution?.attributes?.items;
16739
+ // Case-insensitive lookup: the engine normalises attribute names to
16740
+ // camelCase in the response (`outputData`, `completedAt`) even though
16741
+ // the CK YAML declares them as PascalCase. The Strict reader was a
16742
+ // mistake — it matched nothing in practice and the user always saw
16743
+ // "no validation output yet" no matter how often they ran the pipeline.
16744
+ const outputData = readAttr(attrs, 'OutputData');
16745
+ const completedAt = readAttr(attrs, 'CompletedAt');
16746
+ if (!outputData) {
16747
+ this.dataSource.setValidationMap(new Map());
16748
+ this.validationSummary.set(null);
16749
+ this.validationExecutedAt.set(null);
16750
+ this.validationError.set('Pipeline has no validation output yet — run the pipeline first.');
16751
+ await this.refreshTreeOverlay();
16752
+ return;
16753
+ }
16754
+ const { map: detailMap, summary } = parseValidationReport(outputData);
16755
+ this.dataSource.setValidationMap(detailMap);
16756
+ this.validationSummary.set(summary);
16757
+ this.validationExecutedAt.set(completedAt);
16758
+ await this.refreshTreeOverlay();
16759
+ }
16760
+ catch (error) {
16761
+ console.error('Failed to load validation report:', error);
16762
+ this.validationError.set('Failed to load validation report.');
16763
+ }
16764
+ finally {
16765
+ this.validationLoading.set(false);
16766
+ }
16767
+ }
16768
+ clearValidation() {
16769
+ this.dataSource.setValidationMap(new Map());
16770
+ this.validationSummary.set(null);
16771
+ this.validationExecutedAt.set(null);
16772
+ this.validationError.set(null);
16773
+ void this.refreshTreeOverlay();
16774
+ }
16775
+ // ─── Orphan-Sources Tab ────────────────────────────────────────────────────
16776
+ selectTab(tab) {
16777
+ this.activeTab.set(tab);
16778
+ if (tab === 'orphans' && this.orphanCkType() && this.orphanCandidates().length === 0) {
16779
+ void this.loadOrphanCandidates();
16780
+ }
16781
+ }
16782
+ onOrphanCkTypeChange(ckTypeId) {
16783
+ this.orphanCkType.set(ckTypeId || null);
16784
+ this.orphanCandidates.set([]);
16785
+ if (ckTypeId)
16786
+ void this.loadOrphanCandidates();
16787
+ }
16788
+ async refreshOrphans() {
16789
+ await this.loadOrphanCandidates();
16790
+ }
16791
+ toggleOrphanHideMapped() {
16792
+ this.orphanHideMapped.update(v => !v);
16793
+ }
16794
+ onOrphanGroupParentTypeChange(value) {
16795
+ this.orphanGroupParentType.set(value ? value : null);
16796
+ }
16797
+ /**
16798
+ * Returns the parent chain ordered for breadcrumb display: root-most ancestor
16799
+ * first, immediate parent last. `parentPath` itself is stored immediate-first
16800
+ * (so `parentPath[0]` cheaply reports the grouping key), but humans read
16801
+ * breadcrumbs from outside in.
16802
+ */
16803
+ breadcrumbFor(item) {
16804
+ return [...item.parentPath].reverse();
16805
+ }
16806
+ /**
16807
+ * Fetches all entities of the selected source CK type and tags each with
16808
+ * its inbound MapsFrom DataPointMapping count. The view filters the list
16809
+ * down to mappingCount === 0 by default, but the user can flip the toggle
16810
+ * to see all candidates (mapped + unmapped) for verification.
16811
+ */
16812
+ async loadOrphanCandidates() {
16813
+ const ckTypeId = this.orphanCkType();
16814
+ if (!ckTypeId)
16815
+ return;
16816
+ this.orphanLoading.set(true);
16817
+ this.orphanError.set(null);
16818
+ try {
16819
+ const result = await firstValueFrom(this.getOrphanCandidatesGQL
16820
+ .fetch({
16821
+ variables: {
16822
+ ckTypeId,
16823
+ mapsFromRoleId: this.config.mappingSourceRoleId,
16824
+ mappingCkTypeId: this.config.mappingCkTypeId,
16825
+ childRoleId: this.config.childRoleId,
16826
+ childCkTypeId: this.config.childCkTypeId,
16827
+ first: 1000,
16828
+ after: GraphQL.offsetToCursor(0),
16829
+ },
16830
+ fetchPolicy: 'network-only',
16831
+ })
16832
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items ?? [])));
16833
+ const candidates = (result ?? [])
16834
+ .filter((e) => !!e && !!e.rtId && !!e.ckTypeId)
16835
+ .map(e => ({
16836
+ rtId: e.rtId,
16837
+ ckTypeId: e.ckTypeId,
16838
+ name: readAttr(e.attributes?.items, 'name') ?? e.rtWellKnownName ?? e.rtId,
16839
+ description: readAttr(e.attributes?.items, 'description') ?? undefined,
16840
+ mappingCount: e.associations?.mappings?.totalCount ?? 0,
16841
+ parentPath: extractParentPath(e.associations?.parent?.items?.[0]),
16842
+ }));
16843
+ candidates.sort((a, b) => {
16844
+ // Show unmapped first, then alphabetical.
16845
+ if (a.mappingCount === 0 && b.mappingCount > 0)
16846
+ return -1;
16847
+ if (a.mappingCount > 0 && b.mappingCount === 0)
16848
+ return 1;
16849
+ return a.name.localeCompare(b.name);
16850
+ });
16851
+ this.orphanCandidates.set(candidates);
16852
+ }
16853
+ catch (error) {
16854
+ console.error('Failed to load orphan candidates:', error);
16855
+ this.orphanError.set('Failed to load source candidates.');
16856
+ this.orphanCandidates.set([]);
16857
+ }
16858
+ finally {
16859
+ this.orphanLoading.set(false);
16860
+ }
16861
+ }
16862
+ /**
16863
+ * Opens the mapping editor pre-populated with the orphan as source and lets
16864
+ * the user pick the target + attribute paths. Creates the DataPointMapping
16865
+ * entity atomically on save (both MapsFrom and MapsTo wired up in one
16866
+ * mutation). Cancel leaves nothing behind.
16867
+ */
16868
+ async createMappingFromOrphan(orphan) {
16869
+ const skeleton = {
16870
+ // Empty rtId flags this as a not-yet-persisted mapping; saveEditedMapping
16871
+ // routes it through CreateEntities instead of UpdateRuntimeEntities.
16872
+ rtId: '',
16873
+ ckTypeId: this.config.mappingCkTypeId,
16874
+ name: `${orphan.name} mapping`,
16875
+ enabled: true,
16876
+ sourceRtId: orphan.rtId,
16877
+ sourceCkTypeId: orphan.ckTypeId,
16878
+ sourceName: orphan.name,
16879
+ sourceAttributePath: '',
16880
+ mappingExpression: '',
16881
+ targetAttributePath: '',
16882
+ };
16883
+ const result = await this.editDialog.open({ mapping: skeleton });
16884
+ if (!result.confirmed)
16885
+ return;
16886
+ try {
16887
+ await this.saveEditedMapping(result.mapping);
16888
+ await this.loadOrphanCandidates();
16889
+ }
16890
+ catch (error) {
16891
+ console.error('Failed to create mapping for orphan:', error);
16892
+ this.orphanError.set('Failed to create mapping.');
16893
+ }
16894
+ }
16895
+ trackOrphanByRtId(_index, item) {
16896
+ return item.rtId;
16897
+ }
16898
+ /**
16899
+ * Triggers the selected validation pipeline on the Communication Controller
16900
+ * and, when it completes, automatically refreshes the coverage report so the
16901
+ * tree colour-codes update. Requires {@link tenantId} to be set.
16902
+ *
16903
+ * Polling strategy: every 1.5 s, fetch the latest execution metadata for the
16904
+ * pipeline. When `dateTime` differs from the snapshot taken before the run,
16905
+ * we know a new execution finished — refresh and stop. Aborts after 60 s.
16906
+ */
16907
+ async runValidation() {
16908
+ const pipeline = this.selectedPipeline();
16909
+ const tenant = this.tenantId;
16910
+ if (!pipeline || !tenant) {
16911
+ this.validationError.set(!pipeline ? 'Pick a validation pipeline first.' : 'Tenant context missing — Run is unavailable.');
16912
+ return;
16913
+ }
16914
+ this.validationRunning.set(true);
16915
+ this.validationError.set(null);
16916
+ // Snapshot the latest execution's id so we can detect the new one finishing.
16917
+ let previousExecutionId = null;
16918
+ try {
16919
+ const previous = await this.communicationService.getLatestPipelineExecution(tenant, pipeline.rtId, pipeline.ckTypeId);
16920
+ previousExecutionId = previous?.id ?? null;
16921
+ }
16922
+ catch {
16923
+ // Non-fatal: we'll still detect completion by polling and falling back
16924
+ // to "any latest execution" being non-Running.
16925
+ }
16926
+ try {
16927
+ await this.communicationService.executePipeline(tenant, pipeline.rtId);
16928
+ }
16929
+ catch (error) {
16930
+ console.error('Failed to start validation pipeline:', error);
16931
+ this.validationError.set('Failed to start validation pipeline.');
16932
+ this.validationRunning.set(false);
16933
+ return;
16934
+ }
16935
+ // Poll for completion. We accept that the new execution id may equal
16936
+ // previousExecutionId for a brief moment until the controller flushes it
16937
+ // — that's why we also accept any latest execution whose status leaves
16938
+ // Running (Completed / Failed / Interrupted / Cancelled).
16939
+ const startedAt = Date.now();
16940
+ const timeoutMs = 60_000;
16941
+ const pollIntervalMs = 1500;
16942
+ while (Date.now() - startedAt < timeoutMs) {
16943
+ await sleep(pollIntervalMs);
16944
+ try {
16945
+ const latest = await this.communicationService.getLatestPipelineExecution(tenant, pipeline.rtId, pipeline.ckTypeId);
16946
+ if (!latest)
16947
+ continue;
16948
+ const idChanged = latest.id !== previousExecutionId;
16949
+ // Treat absent status as "done". The in-memory debug-cache branch of
16950
+ // GET /pipelineDebug/.../latest doesn't populate Status (only the
16951
+ // MongoDB fallback does), so a sub-second pipeline that hits the
16952
+ // cache path before the DB row is visible would never satisfy a
16953
+ // strict `status === 'Completed'` check and the button would freeze.
16954
+ // We only keep polling while the controller *explicitly* reports
16955
+ // "Running" (case-insensitive for safety).
16956
+ const stillRunning = typeof latest.status === 'string' && latest.status.toLowerCase() === 'running';
16957
+ if (idChanged && !stillRunning) {
16958
+ this.validationRunning.set(false);
16959
+ await this.refreshValidation();
16960
+ return;
16961
+ }
16962
+ }
16963
+ catch (error) {
16964
+ console.warn('Polling validation execution failed:', error);
16965
+ }
16966
+ }
16967
+ // Timeout: leave running flag off so the button is usable again; user can
16968
+ // hit Load Report manually if the pipeline is just slow.
16969
+ this.validationRunning.set(false);
16970
+ this.validationError.set('Validation is still running — use Load Report to refresh later.');
16971
+ }
16972
+ async refreshTreeOverlay() {
16973
+ if (this.treeView?.isReady) {
16974
+ const expanded = this.treeView.getExpandedKeys();
16975
+ await this.treeView.refreshTree();
16976
+ this.treeView.setExpandedKeys(expanded);
16977
+ }
16978
+ // Re-evaluate the currently selected node's payload so the detail panel
16979
+ // shows the new validation status.
16980
+ const current = this.selectedNode();
16981
+ if (current) {
16982
+ const refreshed = await this.dataSource.refreshNode(current.rtId, current.ckTypeId);
16983
+ if (refreshed)
16984
+ this.selectedNode.set(refreshed);
16985
+ }
16986
+ }
16987
+ async loadValidationPipelines() {
16988
+ try {
16989
+ const result = await firstValueFrom(this.getEntitiesByCkType
16990
+ .fetch({
16991
+ variables: {
16992
+ ckTypeId: this.config.validationPipelineCkTypeId,
16993
+ first: 200,
16994
+ after: GraphQL.offsetToCursor(0),
16995
+ },
16996
+ fetchPolicy: 'network-only',
16997
+ })
16998
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items ?? [])));
16999
+ const candidates = (result ?? [])
17000
+ .filter((e) => !!e && !!e.rtId && !!e.ckTypeId)
17001
+ .map(e => ({
17002
+ rtId: e.rtId,
17003
+ ckTypeId: e.ckTypeId,
17004
+ name: readAttr(e.attributes?.items, 'name') ?? e.rtWellKnownName ?? e.rtId,
17005
+ }));
17006
+ candidates.sort((a, b) => a.name.localeCompare(b.name));
17007
+ this.validationPipelines.set(candidates);
17008
+ }
17009
+ catch (error) {
17010
+ console.error('Failed to load validation pipelines:', error);
17011
+ this.validationPipelines.set([]);
17012
+ }
17013
+ }
17014
+ async refreshRoots() {
17015
+ await this.loadRootCandidates();
17016
+ }
17017
+ async pickRoot() {
17018
+ const result = await this.entitySelector.openEntitySelector({
17019
+ title: `Select ${this.config.rootCkTypeId}`,
17020
+ });
17021
+ if (!result.confirmed || !result.entity)
17022
+ return;
17023
+ const candidate = {
17024
+ rtId: result.entity.rtId,
17025
+ ckTypeId: result.entity.ckTypeId,
17026
+ name: result.entity.name ?? result.entity.rtId,
17027
+ description: '',
17028
+ };
17029
+ const known = this.rootCandidates().find(r => r.rtId === candidate.rtId);
17030
+ if (!known) {
17031
+ this.rootCandidates.update(list => [...list, candidate]);
17032
+ }
17033
+ await this.selectRoot(candidate);
17034
+ }
17035
+ async selectRoot(root) {
17036
+ this.selectedRoot.set(root);
17037
+ this.selectedNode.set(null);
17038
+ this.mappings.set([]);
17039
+ this.dataSource.setRoot({
17040
+ rtId: root.rtId,
17041
+ ckTypeId: root.ckTypeId,
17042
+ name: root.name,
17043
+ description: root.description,
17044
+ });
17045
+ if (this.treeView?.isReady) {
17046
+ await this.treeView.refreshTree();
17047
+ }
17048
+ }
17049
+ onRootSelectChange(rtId) {
17050
+ const match = this.rootCandidates().find(r => r.rtId === rtId);
17051
+ if (match) {
17052
+ void this.selectRoot(match);
17053
+ }
17054
+ }
17055
+ async onNodeSelected(item) {
17056
+ const payload = item.item;
17057
+ this.selectedNode.set(payload);
17058
+ this.entitySelected.emit({
17059
+ rtId: payload.rtId,
17060
+ ckTypeId: payload.ckTypeId,
17061
+ name: payload.name,
17062
+ description: payload.description,
17063
+ });
17064
+ await this.loadMappingsForSelected();
17065
+ }
17066
+ async refreshSelected() {
17067
+ const node = this.selectedNode();
17068
+ if (!node)
17069
+ return;
17070
+ const updated = await this.dataSource.refreshNode(node.rtId, node.ckTypeId);
17071
+ if (updated) {
17072
+ this.selectedNode.set(updated);
17073
+ }
17074
+ await this.loadMappingsForSelected();
17075
+ }
17076
+ async addMapping() {
17077
+ const node = this.selectedNode();
17078
+ if (!node)
17079
+ return;
17080
+ try {
17081
+ await firstValueFrom(this.createEntitiesGQL.mutate({
17082
+ variables: {
17083
+ entities: [
17084
+ {
17085
+ ckTypeId: this.config.mappingCkTypeId,
17086
+ attributes: [
17087
+ { attributeName: 'Name', value: `Mapping ${this.mappings().length + 1}` },
17088
+ { attributeName: 'Enabled', value: true },
17089
+ ],
17090
+ associations: [
17091
+ {
17092
+ roleName: this.config.mappingTargetOutboundRoleName,
17093
+ targets: [
17094
+ {
17095
+ modOption: AssociationModOptionsDto.CreateDto,
17096
+ target: { rtId: node.rtId, ckTypeId: node.ckTypeId },
17097
+ },
17098
+ ],
17099
+ },
17100
+ ],
17101
+ },
17102
+ ],
17103
+ },
17104
+ }));
17105
+ await this.refreshSelected();
17106
+ }
17107
+ catch (error) {
17108
+ console.error('Failed to create mapping:', error);
17109
+ this.mappingsError.set('Failed to create mapping.');
17110
+ }
17111
+ }
17112
+ /**
17113
+ * Opens the focused edit dialog for one mapping and, on save, persists
17114
+ * attribute and (if changed) MapsFrom- / MapsTo-association updates in a
17115
+ * single UpdateRuntimeEntities mutation.
17116
+ */
17117
+ async editMapping(mapping) {
17118
+ // In the coverage-tree context the mapping's MapsTo target IS the selected
17119
+ // node — we pre-fill the dialog with that so the user sees it immediately
17120
+ // and can retarget if needed via the new Target Entity picker.
17121
+ const node = this.selectedNode();
17122
+ const initial = {
17123
+ rtId: mapping.rtId,
17124
+ ckTypeId: mapping.ckTypeId,
17125
+ name: mapping.name,
17126
+ enabled: mapping.enabled,
17127
+ sourceRtId: mapping.sourceRtId,
17128
+ sourceCkTypeId: mapping.sourceCkTypeId,
17129
+ sourceName: mapping.sourceName,
17130
+ sourceAttributePath: mapping.sourceAttributePath,
17131
+ mappingExpression: mapping.mappingExpression,
17132
+ targetRtId: node?.rtId,
17133
+ targetCkTypeId: node?.ckTypeId,
17134
+ targetName: node?.name,
17135
+ targetAttributePath: mapping.targetAttributePath,
17136
+ };
17137
+ const result = await this.editDialog.open({ mapping: initial });
17138
+ if (!result.confirmed)
17139
+ return;
17140
+ await this.saveEditedMapping(result.mapping);
17141
+ }
17142
+ async saveEditedMapping(edited) {
17143
+ const attributeUpdates = [
17144
+ { attributeName: 'Name', value: edited.name ?? '' },
17145
+ { attributeName: 'Enabled', value: edited.enabled },
17146
+ { attributeName: 'SourceAttributePath', value: edited.sourceAttributePath ?? '' },
17147
+ { attributeName: 'MappingExpression', value: edited.mappingExpression ?? '' },
17148
+ { attributeName: 'TargetAttributePath', value: edited.targetAttributePath ?? '' },
17149
+ ];
17150
+ const associations = [];
17151
+ const buildAssocChange = (originalRtId, originalCkTypeId, newRtId, newCkTypeId, roleName) => {
17152
+ if (newRtId === originalRtId && newCkTypeId === originalCkTypeId)
17153
+ return;
17154
+ const targets = [];
17155
+ if (originalRtId && originalCkTypeId) {
17156
+ targets.push({
17157
+ modOption: AssociationModOptionsDto.DeleteDto,
17158
+ target: { rtId: originalRtId, ckTypeId: originalCkTypeId },
17159
+ });
17160
+ }
17161
+ if (newRtId && newCkTypeId) {
17162
+ targets.push({
17163
+ modOption: AssociationModOptionsDto.CreateDto,
17164
+ target: { rtId: newRtId, ckTypeId: newCkTypeId },
17165
+ });
17166
+ }
17167
+ if (targets.length > 0) {
17168
+ associations.push({ roleName, targets });
17169
+ }
17170
+ };
17171
+ buildAssocChange(edited._originalSourceRtId, edited._originalSourceCkTypeId, edited.sourceRtId, edited.sourceCkTypeId, this.config.mappingSourceOutboundRoleName);
17172
+ buildAssocChange(edited._originalTargetRtId, edited._originalTargetCkTypeId, edited.targetRtId, edited.targetCkTypeId, this.config.mappingTargetOutboundRoleName);
17173
+ try {
17174
+ if (!edited.rtId) {
17175
+ // New mapping (orphan-flow): atomic create with both MapsFrom and
17176
+ // MapsTo associations. We translate the assoc-change list (built for
17177
+ // the update path's modOptions) into a create-only assoc list — the
17178
+ // entity doesn't exist yet, so any deletes are no-ops.
17179
+ const createAssociations = associations
17180
+ .map(a => ({
17181
+ roleName: a.roleName,
17182
+ targets: a.targets
17183
+ .filter(t => t.modOption !== AssociationModOptionsDto.DeleteDto)
17184
+ .map(t => ({ modOption: AssociationModOptionsDto.CreateDto, target: t.target })),
17185
+ }))
17186
+ .filter(a => a.targets.length > 0);
17187
+ await firstValueFrom(this.createEntitiesGQL.mutate({
17188
+ variables: {
17189
+ entities: [
17190
+ {
17191
+ ckTypeId: edited.ckTypeId,
17192
+ attributes: attributeUpdates,
17193
+ associations: createAssociations,
17194
+ },
17195
+ ],
17196
+ },
17197
+ }));
17198
+ }
17199
+ else {
17200
+ await firstValueFrom(this.updateEntitiesGQL.mutate({
17201
+ variables: {
17202
+ entities: [
17203
+ {
17204
+ rtId: edited.rtId,
17205
+ item: {
17206
+ ckTypeId: edited.ckTypeId,
17207
+ attributes: attributeUpdates,
17208
+ associations,
17209
+ },
17210
+ },
17211
+ ],
17212
+ },
17213
+ }));
17214
+ }
17215
+ await this.refreshSelected();
17216
+ }
17217
+ catch (error) {
17218
+ console.error('Failed to save mapping:', error);
17219
+ this.mappingsError.set('Failed to save mapping changes.');
17220
+ }
17221
+ }
17222
+ async deleteMapping(mapping) {
17223
+ const confirmed = await this.confirmation.showYesNoConfirmationDialog('Delete Mapping', `Delete mapping '${mapping.name || mapping.rtId}'? This cannot be undone.`);
17224
+ if (!confirmed)
17225
+ return;
17226
+ try {
17227
+ await firstValueFrom(this.deleteEntitiesGQL.mutate({
17228
+ variables: {
17229
+ rtEntityIds: [{ rtId: mapping.rtId, ckTypeId: mapping.ckTypeId }],
17230
+ deleteStrategy: DeleteStrategiesDto.EraseDto,
17231
+ },
17232
+ }));
17233
+ await this.refreshSelected();
17234
+ }
17235
+ catch (error) {
17236
+ console.error('Failed to delete mapping:', error);
17237
+ this.mappingsError.set('Failed to delete mapping.');
17238
+ }
17239
+ }
17240
+ trackByRtId(_index, item) {
17241
+ return item.rtId;
17242
+ }
17243
+ async loadRootCandidates() {
17244
+ try {
17245
+ const result = await firstValueFrom(this.getEntitiesByCkType
17246
+ .fetch({
17247
+ variables: {
17248
+ ckTypeId: this.config.rootCkTypeId,
17249
+ first: 200,
17250
+ after: GraphQL.offsetToCursor(0),
17251
+ },
17252
+ fetchPolicy: 'network-only',
17253
+ })
17254
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items ?? [])));
17255
+ const candidates = (result ?? [])
17256
+ .filter((e) => !!e && !!e.rtId && !!e.ckTypeId)
17257
+ .map(e => {
17258
+ const name = readAttr(e.attributes?.items, 'name') ?? e.rtWellKnownName ?? e.rtId;
17259
+ const description = readAttr(e.attributes?.items, 'description') ?? '';
17260
+ return {
17261
+ rtId: e.rtId,
17262
+ ckTypeId: e.ckTypeId,
17263
+ name,
17264
+ description,
17265
+ };
17266
+ });
17267
+ candidates.sort((a, b) => a.name.localeCompare(b.name));
17268
+ this.rootCandidates.set(candidates);
17269
+ }
17270
+ catch (error) {
17271
+ console.error('Failed to load root candidates:', error);
17272
+ this.rootCandidates.set([]);
17273
+ }
17274
+ }
17275
+ async loadMappingsForSelected() {
17276
+ const node = this.selectedNode();
17277
+ if (!node) {
17278
+ this.mappings.set([]);
17279
+ return;
17280
+ }
17281
+ this.mappingsLoading.set(true);
17282
+ this.mappingsError.set(null);
17283
+ try {
17284
+ const data = await firstValueFrom(this.getNodeMappingsGQL
17285
+ .fetch({
17286
+ variables: {
17287
+ rtId: node.rtId,
17288
+ ckTypeId: node.ckTypeId,
17289
+ mapsToRoleId: this.config.mappingRoleId,
17290
+ mapsFromRoleId: this.config.mappingSourceRoleId,
17291
+ mappingCkTypeId: this.config.mappingCkTypeId,
17292
+ },
17293
+ fetchPolicy: 'network-only',
17294
+ })
17295
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items?.[0])));
17296
+ const items = data?.associations?.mappings?.items ?? [];
17297
+ // First pass: map mapping attributes + source rtId/ckTypeId from association
17298
+ // definitions. Source NAMES are resolved in a second pass below because the
17299
+ // generic `targets(ckId: ...)` projection requires a concrete CK type with
17300
+ // its own MongoDB collection — `System/Entity` is abstract and would error.
17301
+ const mapped = items
17302
+ .filter((m) => !!m && !!m.rtId && !!m.ckTypeId)
17303
+ .map(m => {
17304
+ const attrs = m.attributes?.items ?? [];
17305
+ const sourceDef = m.associations?.sources?.items?.[0] ?? null;
17306
+ return {
17307
+ rtId: m.rtId,
17308
+ ckTypeId: m.ckTypeId,
17309
+ name: readAttr(attrs, 'name') ?? '',
17310
+ enabled: readAttr(attrs, 'enabled') !== 'false',
17311
+ sourceAttributePath: readAttr(attrs, 'sourceAttributePath') ?? '',
17312
+ targetAttributePath: readAttr(attrs, 'targetAttributePath') ?? '',
17313
+ mappingExpression: readAttr(attrs, 'mappingExpression') ?? '',
17314
+ sourceRtId: sourceDef?.targetRtId ? String(sourceDef.targetRtId) : undefined,
17315
+ sourceCkTypeId: sourceDef?.targetCkTypeId ? String(sourceDef.targetCkTypeId) : undefined,
17316
+ sourceName: undefined,
17317
+ };
17318
+ });
17319
+ this.mappings.set(mapped);
17320
+ // Fire-and-forget the source-name resolution in parallel; the list renders
17321
+ // immediately and names patch in as they arrive.
17322
+ void this.resolveSourceNames(mapped);
17323
+ }
17324
+ catch (error) {
17325
+ console.error('Failed to load mappings:', error);
17326
+ this.mappingsError.set('Failed to load mappings.');
17327
+ this.mappings.set([]);
17328
+ }
17329
+ finally {
17330
+ this.mappingsLoading.set(false);
17331
+ }
17332
+ }
17333
+ /**
17334
+ * Resolves source entity names by issuing a parallel `getRuntimeEntityById`
17335
+ * per unique (rtId, ckTypeId) pair. Updates the mappings signal in-place so
17336
+ * the detail panel re-renders with names. Failures are silent — the row
17337
+ * still falls back to displaying the source rtId.
17338
+ */
17339
+ async resolveSourceNames(mappings) {
17340
+ const byKey = new Map();
17341
+ for (const m of mappings) {
17342
+ if (m.sourceRtId && m.sourceCkTypeId) {
17343
+ byKey.set(`${m.sourceCkTypeId}@${m.sourceRtId}`, {
17344
+ rtId: m.sourceRtId,
17345
+ ckTypeId: m.sourceCkTypeId,
17346
+ });
17347
+ }
17348
+ }
17349
+ if (byKey.size === 0)
17350
+ return;
17351
+ const lookups = await Promise.all(Array.from(byKey.values()).map(async (ref) => {
17352
+ try {
17353
+ const data = await firstValueFrom(this.getRuntimeEntityByIdGQL
17354
+ .fetch({
17355
+ variables: { rtId: ref.rtId, ckTypeId: ref.ckTypeId },
17356
+ fetchPolicy: 'cache-first',
17357
+ })
17358
+ .pipe(map(r => r.data?.runtime?.runtimeEntities?.items?.[0])));
17359
+ const name = readAttr(data?.attributes?.items, 'name') ?? data?.rtWellKnownName ?? null;
17360
+ return { key: `${ref.ckTypeId}@${ref.rtId}`, name };
17361
+ }
17362
+ catch {
17363
+ return { key: `${ref.ckTypeId}@${ref.rtId}`, name: null };
17364
+ }
17365
+ }));
17366
+ const nameByKey = new Map(lookups.map(l => [l.key, l.name]));
17367
+ const node = this.selectedNode();
17368
+ if (!node)
17369
+ return; // user changed selection meanwhile
17370
+ const next = this.mappings().map(m => {
17371
+ if (!m.sourceRtId || !m.sourceCkTypeId)
17372
+ return m;
17373
+ const key = `${m.sourceCkTypeId}@${m.sourceRtId}`;
17374
+ const name = nameByKey.get(key);
17375
+ return name ? { ...m, sourceName: name } : m;
17376
+ });
17377
+ this.mappings.set(next);
17378
+ }
17379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingCoverageTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MappingCoverageTreeComponent, isStandalone: true, selector: "mm-mapping-coverage-tree", inputs: { config: "config", initialRoot: "initialRoot", tenantId: "tenantId" }, outputs: { entitySelected: "entitySelected" }, providers: [MappingCoverageTreeDataSource], viewQueries: [{ propertyName: "treeView", first: true, predicate: TreeComponent, descendants: true }], ngImport: i0, template: "<div class=\"coverage-root\">\n @if (config.sourceCandidateCkTypeIds.length > 0) {\n <div class=\"coverage-tabs\" role=\"tablist\">\n <button class=\"coverage-tab\"\n [class.active]=\"activeTab() === 'coverage'\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === 'coverage'\"\n (click)=\"selectTab('coverage')\">Coverage Tree</button>\n <button class=\"coverage-tab\"\n [class.active]=\"activeTab() === 'orphans'\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === 'orphans'\"\n (click)=\"selectTab('orphans')\">\n Orphan Sources\n @if (orphanStats(); as s) {\n @if (s.unmapped > 0) { <span class=\"tab-badge\">{{ s.unmapped }}</span> }\n }\n </button>\n </div>\n }\n\n @if (activeTab() === 'coverage') {\n <div class=\"coverage-toolbar\">\n <label class=\"toolbar-label\">Tree:</label>\n <select class=\"root-select\"\n [value]=\"selectedRoot()?.rtId ?? ''\"\n (change)=\"onRootSelectChange($any($event.target).value)\">\n <option value=\"\" disabled>\u2014 Pick a {{ config.rootCkTypeId }} \u2014</option>\n @for (root of rootCandidates(); track root.rtId) {\n <option [value]=\"root.rtId\" [title]=\"root.description\">{{ root.name }}</option>\n }\n </select>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n (click)=\"refreshRoots()\" title=\"Refresh tree list\"></button>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.folderOpen\"\n (click)=\"pickRoot()\" title=\"Browse for entity\">Browse...</button>\n\n @if (selectedRoot(); as root) {\n <div class=\"toolbar-spacer\"></div>\n <span class=\"toolbar-meta\">\n <span class=\"meta-name\">{{ root.name }}</span>\n <span class=\"meta-type\">{{ root.ckTypeId }}</span>\n </span>\n }\n </div>\n\n <div class=\"coverage-toolbar coverage-toolbar-validation\">\n <label class=\"toolbar-label\">Validation:</label>\n <select class=\"root-select\"\n [value]=\"selectedPipeline()?.rtId ?? ''\"\n (change)=\"onPipelineSelectChange($any($event.target).value)\">\n <option value=\"\" disabled>\u2014 Pick a {{ config.validationPipelineCkTypeId }} \u2014</option>\n @for (p of validationPipelines(); track p.rtId) {\n <option [value]=\"p.rtId\">{{ p.name }}</option>\n }\n </select>\n @if (tenantId) {\n <button kendoButton themeColor=\"primary\" size=\"small\"\n [disabled]=\"!selectedPipeline() || validationRunning() || validationLoading()\"\n (click)=\"runValidation()\"\n [title]=\"validationRunning() ? 'Validation running\u2026' : 'Run pipeline + load report'\">\n @if (validationRunning()) { Running\u2026 } @else { Run }\n </button>\n }\n <button kendoButton fillMode=\"solid\" size=\"small\" [svgIcon]=\"icons.refresh\"\n [disabled]=\"!selectedPipeline() || validationLoading() || validationRunning()\"\n (click)=\"refreshValidation()\">Load Report</button>\n @if (validationSummary()) {\n <button kendoButton fillMode=\"flat\" size=\"small\"\n (click)=\"clearValidation()\">Clear</button>\n }\n\n <div class=\"toolbar-spacer\"></div>\n\n @if (validationLoading()) {\n <span class=\"toolbar-meta\">Loading\u2026</span>\n } @else if (validationError(); as err) {\n <span class=\"toolbar-meta validation-error-text\">{{ err }}</span>\n } @else if (validationSummary(); as s) {\n <span class=\"validation-summary\">\n <span class=\"badge badge-error\" title=\"error\">{{ s.error }} err</span>\n <span class=\"badge badge-warning\" title=\"warning\">{{ s.warning }} warn</span>\n <span class=\"badge badge-ok\" title=\"ok\">{{ s.ok }} ok</span>\n <span class=\"badge badge-info\" title=\"info / unscoped\">{{ s.info }} info</span>\n <span class=\"badge\">{{ s.total }} total</span>\n @if (validationExecutedAt(); as ts) {\n <span class=\"validation-timestamp\">{{ ts }}</span>\n }\n </span>\n }\n </div>\n\n <div class=\"coverage-body\">\n <div class=\"coverage-tree-pane\">\n @if (selectedRoot()) {\n <mm-tree-view\n [dataSource]=\"dataSource\"\n (nodeSelected)=\"onNodeSelected($event)\">\n </mm-tree-view>\n } @else {\n <div class=\"empty-hint\">\n Select a tree to start. Counts in brackets show how many mappings\n point to that node.\n </div>\n }\n </div>\n\n <div class=\"coverage-detail-pane\">\n @if (selectedNode(); as node) {\n <div class=\"detail-header\">\n <div class=\"detail-titles\">\n <h3 class=\"detail-name\">{{ node.name }}</h3>\n <div class=\"detail-subtitle\">{{ node.ckTypeId }}@{{ node.rtId }}</div>\n </div>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n (click)=\"refreshSelected()\" title=\"Refresh\"></button>\n </div>\n\n <div class=\"detail-section-title\">\n <span>Mappings</span>\n @if (summaryLine(); as s) {\n <span class=\"detail-summary\">{{ s }}</span>\n }\n <span class=\"toolbar-spacer\"></span>\n <button kendoButton themeColor=\"primary\" size=\"small\" [svgIcon]=\"icons.plus\"\n (click)=\"addMapping()\">Add</button>\n </div>\n\n @if (mappingsLoading()) {\n <div class=\"empty-hint\">Loading mappings\u2026</div>\n } @else if (mappingsError(); as err) {\n <div class=\"error-hint\">{{ err }}</div>\n } @else if (mappings().length === 0) {\n <div class=\"empty-hint\">\n No mappings reference this node yet. Add one using the button above.\n </div>\n } @else {\n <ul class=\"mapping-list\">\n @for (mapping of mappings(); track trackByRtId($index, mapping)) {\n <li class=\"mapping-row\" [class.mapping-disabled]=\"!mapping.enabled\">\n <div class=\"mapping-line\">\n <span class=\"mapping-name\">\n {{ mapping.name || 'Mapping ' + ($index + 1) }}\n </span>\n @if (!mapping.enabled) {\n <span class=\"mapping-badge mapping-badge-off\">disabled</span>\n }\n </div>\n <div class=\"mapping-details\">\n <span class=\"kv\">\n <span class=\"kv-key\">Source:</span>\n <span class=\"kv-val\">\n {{ mapping.sourceName || mapping.sourceRtId || '\u2014 not linked \u2014' }}\n </span>\n </span>\n @if (mapping.sourceAttributePath) {\n <span class=\"kv\">\n <span class=\"kv-key\">Attr:</span>\n <span class=\"kv-val\">{{ mapping.sourceAttributePath }}</span>\n </span>\n }\n @if (mapping.mappingExpression) {\n <span class=\"kv\">\n <span class=\"kv-key\">Expr:</span>\n <code class=\"kv-val\">{{ mapping.mappingExpression }}</code>\n </span>\n }\n @if (mapping.targetAttributePath) {\n <span class=\"kv\">\n <span class=\"kv-key\">\u2192</span>\n <span class=\"kv-val\">{{ mapping.targetAttributePath }}</span>\n </span>\n }\n </div>\n <div class=\"mapping-actions\">\n <button kendoButton themeColor=\"primary\" fillMode=\"flat\" size=\"small\"\n [svgIcon]=\"icons.pencil\"\n (click)=\"editMapping(mapping)\"\n title=\"Edit mapping\">Edit\u2026</button>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.trash\"\n (click)=\"deleteMapping(mapping)\"\n title=\"Delete mapping\"></button>\n </div>\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"empty-hint\">\n Select a node from the tree to view and edit its mappings.\n </div>\n }\n </div>\n </div>\n }\n\n @if (activeTab() === 'orphans') {\n <div class=\"coverage-toolbar\">\n <label class=\"toolbar-label\">Source type:</label>\n <select class=\"root-select\"\n [value]=\"orphanCkType() ?? ''\"\n (change)=\"onOrphanCkTypeChange($any($event.target).value)\">\n @for (ckType of config.sourceCandidateCkTypeIds; track ckType) {\n <option [value]=\"ckType\">{{ ckType }}</option>\n }\n </select>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n [disabled]=\"!orphanCkType() || orphanLoading()\"\n (click)=\"refreshOrphans()\" title=\"Reload\"></button>\n <label class=\"orphan-toggle\">\n <input type=\"checkbox\" [checked]=\"orphanHideMapped()\" (change)=\"toggleOrphanHideMapped()\" />\n Hide already-mapped\n </label>\n <label class=\"toolbar-label\">Group by:</label>\n <select class=\"root-select\"\n [value]=\"orphanGroupParentType() ?? ''\"\n (change)=\"onOrphanGroupParentTypeChange($any($event.target).value)\">\n <option value=\"\">(no grouping)</option>\n @for (t of orphanAvailableParentTypes(); track t) {\n <option [value]=\"t\">{{ t }}</option>\n }\n </select>\n\n <div class=\"toolbar-spacer\"></div>\n\n @if (orphanStats(); as s) {\n <span class=\"validation-summary\">\n <span class=\"badge badge-error\" title=\"unmapped \u2014 no DataPointMapping yet\">{{ s.unmapped }} unmapped</span>\n <span class=\"badge badge-ok\" title=\"already covered by at least one mapping\">{{ s.mapped }} mapped</span>\n <span class=\"badge\">{{ s.total }} total</span>\n </span>\n }\n </div>\n\n <div class=\"orphan-body\">\n @if (orphanLoading()) {\n <div class=\"empty-hint\">Loading source candidates\u2026</div>\n } @else if (orphanError(); as err) {\n <div class=\"error-hint\">{{ err }}</div>\n } @else if (orphanFilteredList().length === 0) {\n <div class=\"empty-hint\">\n @if (orphanHideMapped()) {\n All {{ orphanCkType() }} entities are mapped at least once. \uD83C\uDF89\n } @else {\n No entities of type {{ orphanCkType() }} found.\n }\n </div>\n } @else if (orphanGroupParentType()) {\n @for (group of orphanGroupedList(); track group.key) {\n <div class=\"orphan-group\">\n <div class=\"orphan-group-header\">\n <span class=\"orphan-group-label\">{{ group.label }}</span>\n <span class=\"orphan-group-count\">{{ group.items.length }}</span>\n </div>\n <ul class=\"orphan-list\">\n @for (item of group.items; track trackOrphanByRtId($index, item)) {\n <ng-container *ngTemplateOutlet=\"orphanRowTpl; context: { $implicit: item }\"></ng-container>\n }\n </ul>\n </div>\n }\n } @else {\n <ul class=\"orphan-list\">\n @for (item of orphanFilteredList(); track trackOrphanByRtId($index, item)) {\n <ng-container *ngTemplateOutlet=\"orphanRowTpl; context: { $implicit: item }\"></ng-container>\n }\n </ul>\n }\n\n <ng-template #orphanRowTpl let-item>\n <li class=\"orphan-row\" [class.orphan-mapped]=\"item.mappingCount > 0\">\n <div class=\"orphan-line\">\n <span class=\"orphan-name\">{{ item.name }}</span>\n @if (item.mappingCount > 0) {\n <span class=\"orphan-badge orphan-badge-mapped\">\n {{ item.mappingCount }} mapping{{ item.mappingCount === 1 ? '' : 's' }}\n </span>\n } @else {\n <span class=\"orphan-badge orphan-badge-unmapped\">unmapped</span>\n }\n </div>\n @if (item.parentPath.length > 0) {\n <div class=\"orphan-breadcrumb\" [attr.title]=\"'Parent chain (root-first)'\">\n @for (parent of breadcrumbFor(item); track parent.rtId; let last = $last) {\n <span class=\"crumb\">{{ parent.name }}</span>\n @if (!last) { <span class=\"crumb-sep\">\u203A</span> }\n }\n </div>\n }\n <div class=\"orphan-meta\">\n <span class=\"kv-val\">{{ item.ckTypeId }}&#64;{{ item.rtId }}</span>\n </div>\n @if (item.description) {\n <div class=\"orphan-description\">{{ item.description }}</div>\n }\n <div class=\"orphan-actions\">\n <button kendoButton themeColor=\"primary\" fillMode=\"flat\" size=\"small\"\n [svgIcon]=\"icons.plus\"\n (click)=\"createMappingFromOrphan(item)\"\n title=\"Create mapping pre-linked to this source\">\n Map\u2026\n </button>\n </div>\n </li>\n </ng-template>\n </div>\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}.coverage-root{display:flex;flex-direction:column;flex:1;min-height:0;gap:12px}.coverage-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 12px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface-alt, #f8f9fa)) 70%,transparent)}.coverage-toolbar .toolbar-label{flex:0 0 96px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.coverage-toolbar .root-select{flex:0 0 280px;min-width:0;padding:4px 8px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:4px;background:var(--theme-bg-elevated, var(--kendo-color-surface, #fff));color:var(--theme-text-primary, inherit);font-size:.9rem;height:30px}.coverage-toolbar .toolbar-spacer{flex:1}.coverage-toolbar .toolbar-meta{display:flex;flex-direction:column;align-items:flex-end;line-height:1.1}.coverage-toolbar .toolbar-meta .meta-name{font-size:.85rem;font-weight:600}.coverage-toolbar .toolbar-meta .meta-type{font-size:.7rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.coverage-body{flex:1;display:flex;gap:12px;min-height:0}.coverage-tree-pane{flex:0 0 40%;min-width:240px;display:flex;flex-direction:column;min-height:0;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);color:var(--theme-text-primary, inherit);overflow:auto}.coverage-detail-pane{flex:1;min-width:0;display:flex;flex-direction:column;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);color:var(--theme-text-primary, inherit);padding:12px 14px;gap:12px;overflow:auto}.detail-header{display:flex;align-items:flex-start;gap:8px}.detail-header .detail-titles{flex:1;min-width:0}.detail-header .detail-name{margin:0;font-size:1.05rem;font-weight:600;word-break:break-word}.detail-header .detail-subtitle{font-size:.72rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d);word-break:break-all}.detail-section-title{display:flex;align-items:center;gap:10px;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.detail-section-title .toolbar-spacer{flex:1}.detail-section-title .detail-summary{text-transform:none;letter-spacing:0;font-weight:500;color:var(--kendo-color-on-app-surface, inherit);font-size:.78rem}.mapping-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mapping-row{border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.mapping-row.mapping-disabled{opacity:.55}.mapping-line{display:flex;align-items:center;gap:8px}.mapping-line .mapping-name{font-weight:600;font-size:.9rem}.mapping-badge{font-size:.65rem;text-transform:uppercase;letter-spacing:.5px;padding:1px 6px;border-radius:8px;border:1px solid var(--kendo-color-border, #dee2e6)}.mapping-badge.mapping-badge-off{color:var(--kendo-color-subtle, #6c757d)}.mapping-details{display:flex;flex-wrap:wrap;gap:10px;font-size:.8rem}.mapping-details .kv{display:inline-flex;align-items:baseline;gap:4px}.mapping-details .kv-key{font-weight:600;color:var(--kendo-color-subtle, #6c757d)}.mapping-details code.kv-val{font-family:monospace;background:var(--kendo-color-surface, transparent);padding:1px 4px;border-radius:3px}.mapping-actions{display:flex;gap:6px;justify-content:flex-end}.empty-hint,.error-hint{padding:12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d);text-align:center}.error-hint{color:var(--kendo-color-error, #dc3545)}.coverage-tabs{display:flex;gap:0;border-bottom:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6))}.coverage-tab{padding:8px 16px;border:none;background:transparent;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-size:.85rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;display:inline-flex;align-items:center;gap:6px}.coverage-tab:hover{color:var(--theme-text-primary, inherit)}.coverage-tab.active{color:var(--theme-text-accent, var(--kendo-color-primary, #ff6358));border-bottom-color:var(--theme-text-accent, var(--kendo-color-primary, #ff6358))}.coverage-tab .tab-badge{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 22%,transparent);color:var(--kendo-color-error, #dc3545);border-radius:10px;padding:1px 8px;font-size:.7rem;font-weight:700}.orphan-toggle{display:inline-flex;align-items:center;gap:6px;font-size:.8rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));cursor:pointer}.orphan-body{flex:1;min-height:0;overflow:auto;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);padding:8px}.orphan-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.orphan-row{display:flex;flex-direction:column;gap:4px;padding:8px 10px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-left-width:3px;border-left-color:var(--kendo-color-error, #dc3545);border-radius:6px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.orphan-row.orphan-mapped{border-left-color:var(--kendo-color-success, #28a745);opacity:.7}.orphan-row .orphan-line{display:flex;align-items:center;gap:8px}.orphan-row .orphan-line .orphan-name{flex:1;font-weight:600;font-size:.9rem}.orphan-row .orphan-badge{font-size:.7rem;padding:1px 8px;border-radius:10px;text-transform:uppercase;letter-spacing:.5px;font-weight:600}.orphan-row .orphan-badge-unmapped{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 22%,transparent);color:var(--kendo-color-error, #dc3545);border:1px solid var(--kendo-color-error, #dc3545)}.orphan-row .orphan-badge-mapped{background:color-mix(in srgb,var(--kendo-color-success, #28a745) 18%,transparent);color:var(--kendo-color-success, #28a745);border:1px solid var(--kendo-color-success, #28a745)}.orphan-row .orphan-meta{font-family:monospace;font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));word-break:break-all}.orphan-row .orphan-breadcrumb{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.75rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.orphan-row .orphan-breadcrumb .crumb{line-height:1.3}.orphan-row .orphan-breadcrumb .crumb-sep{opacity:.55;font-weight:600}.orphan-row .orphan-description{font-size:.78rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-style:italic}.orphan-row .orphan-actions{display:flex;gap:6px;justify-content:flex-end}.orphan-group{display:flex;flex-direction:column;gap:6px}.orphan-group+.orphan-group{margin-top:14px}.orphan-group .orphan-group-header{display:flex;align-items:baseline;gap:8px;padding:4px 8px;border-bottom:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));font-size:.85rem;font-weight:600;color:var(--theme-text-primary, inherit)}.orphan-group .orphan-group-header .orphan-group-label{flex:1}.orphan-group .orphan-group-header .orphan-group-count{font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f1f3f5)) 80%,transparent);border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:10px;padding:0 8px}.validation-summary{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}.badge{font-size:.7rem;padding:2px 8px;border-radius:10px;background:var(--kendo-color-surface, #f1f3f5);border:1px solid var(--kendo-color-border, #dee2e6)}.badge-error{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 18%,transparent);border-color:var(--kendo-color-error, #dc3545);color:var(--kendo-color-error, #dc3545);font-weight:600}.badge-warning{background:color-mix(in srgb,var(--kendo-color-warning, #ffc107) 22%,transparent);border-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-warning-on, #6b5500);font-weight:600}.badge-ok{background:color-mix(in srgb,var(--kendo-color-success, #28a745) 18%,transparent);border-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-success, #28a745);font-weight:600}.badge-info{background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);border-color:var(--kendo-color-info, #0dcaf0);color:var(--kendo-color-info, #0dcaf0)}.validation-error-text{color:var(--kendo-color-error, #dc3545);font-size:.8rem}.validation-timestamp{font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);margin-left:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: LayoutModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: TreeComponent, selector: "mm-tree-view", inputs: ["dataSource"], outputs: ["nodeSelected", "nodeClick", "nodeDoubleClick", "nodeDrop", "expand", "collapse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17381
+ }
17382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MappingCoverageTreeComponent, decorators: [{
17383
+ type: Component,
17384
+ args: [{ selector: 'mm-mapping-coverage-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
17385
+ CommonModule,
17386
+ FormsModule,
17387
+ ButtonModule,
17388
+ LayoutModule,
17389
+ SVGIconModule,
17390
+ TreeComponent,
17391
+ ], providers: [MappingCoverageTreeDataSource], template: "<div class=\"coverage-root\">\n @if (config.sourceCandidateCkTypeIds.length > 0) {\n <div class=\"coverage-tabs\" role=\"tablist\">\n <button class=\"coverage-tab\"\n [class.active]=\"activeTab() === 'coverage'\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === 'coverage'\"\n (click)=\"selectTab('coverage')\">Coverage Tree</button>\n <button class=\"coverage-tab\"\n [class.active]=\"activeTab() === 'orphans'\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === 'orphans'\"\n (click)=\"selectTab('orphans')\">\n Orphan Sources\n @if (orphanStats(); as s) {\n @if (s.unmapped > 0) { <span class=\"tab-badge\">{{ s.unmapped }}</span> }\n }\n </button>\n </div>\n }\n\n @if (activeTab() === 'coverage') {\n <div class=\"coverage-toolbar\">\n <label class=\"toolbar-label\">Tree:</label>\n <select class=\"root-select\"\n [value]=\"selectedRoot()?.rtId ?? ''\"\n (change)=\"onRootSelectChange($any($event.target).value)\">\n <option value=\"\" disabled>\u2014 Pick a {{ config.rootCkTypeId }} \u2014</option>\n @for (root of rootCandidates(); track root.rtId) {\n <option [value]=\"root.rtId\" [title]=\"root.description\">{{ root.name }}</option>\n }\n </select>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n (click)=\"refreshRoots()\" title=\"Refresh tree list\"></button>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.folderOpen\"\n (click)=\"pickRoot()\" title=\"Browse for entity\">Browse...</button>\n\n @if (selectedRoot(); as root) {\n <div class=\"toolbar-spacer\"></div>\n <span class=\"toolbar-meta\">\n <span class=\"meta-name\">{{ root.name }}</span>\n <span class=\"meta-type\">{{ root.ckTypeId }}</span>\n </span>\n }\n </div>\n\n <div class=\"coverage-toolbar coverage-toolbar-validation\">\n <label class=\"toolbar-label\">Validation:</label>\n <select class=\"root-select\"\n [value]=\"selectedPipeline()?.rtId ?? ''\"\n (change)=\"onPipelineSelectChange($any($event.target).value)\">\n <option value=\"\" disabled>\u2014 Pick a {{ config.validationPipelineCkTypeId }} \u2014</option>\n @for (p of validationPipelines(); track p.rtId) {\n <option [value]=\"p.rtId\">{{ p.name }}</option>\n }\n </select>\n @if (tenantId) {\n <button kendoButton themeColor=\"primary\" size=\"small\"\n [disabled]=\"!selectedPipeline() || validationRunning() || validationLoading()\"\n (click)=\"runValidation()\"\n [title]=\"validationRunning() ? 'Validation running\u2026' : 'Run pipeline + load report'\">\n @if (validationRunning()) { Running\u2026 } @else { Run }\n </button>\n }\n <button kendoButton fillMode=\"solid\" size=\"small\" [svgIcon]=\"icons.refresh\"\n [disabled]=\"!selectedPipeline() || validationLoading() || validationRunning()\"\n (click)=\"refreshValidation()\">Load Report</button>\n @if (validationSummary()) {\n <button kendoButton fillMode=\"flat\" size=\"small\"\n (click)=\"clearValidation()\">Clear</button>\n }\n\n <div class=\"toolbar-spacer\"></div>\n\n @if (validationLoading()) {\n <span class=\"toolbar-meta\">Loading\u2026</span>\n } @else if (validationError(); as err) {\n <span class=\"toolbar-meta validation-error-text\">{{ err }}</span>\n } @else if (validationSummary(); as s) {\n <span class=\"validation-summary\">\n <span class=\"badge badge-error\" title=\"error\">{{ s.error }} err</span>\n <span class=\"badge badge-warning\" title=\"warning\">{{ s.warning }} warn</span>\n <span class=\"badge badge-ok\" title=\"ok\">{{ s.ok }} ok</span>\n <span class=\"badge badge-info\" title=\"info / unscoped\">{{ s.info }} info</span>\n <span class=\"badge\">{{ s.total }} total</span>\n @if (validationExecutedAt(); as ts) {\n <span class=\"validation-timestamp\">{{ ts }}</span>\n }\n </span>\n }\n </div>\n\n <div class=\"coverage-body\">\n <div class=\"coverage-tree-pane\">\n @if (selectedRoot()) {\n <mm-tree-view\n [dataSource]=\"dataSource\"\n (nodeSelected)=\"onNodeSelected($event)\">\n </mm-tree-view>\n } @else {\n <div class=\"empty-hint\">\n Select a tree to start. Counts in brackets show how many mappings\n point to that node.\n </div>\n }\n </div>\n\n <div class=\"coverage-detail-pane\">\n @if (selectedNode(); as node) {\n <div class=\"detail-header\">\n <div class=\"detail-titles\">\n <h3 class=\"detail-name\">{{ node.name }}</h3>\n <div class=\"detail-subtitle\">{{ node.ckTypeId }}@{{ node.rtId }}</div>\n </div>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n (click)=\"refreshSelected()\" title=\"Refresh\"></button>\n </div>\n\n <div class=\"detail-section-title\">\n <span>Mappings</span>\n @if (summaryLine(); as s) {\n <span class=\"detail-summary\">{{ s }}</span>\n }\n <span class=\"toolbar-spacer\"></span>\n <button kendoButton themeColor=\"primary\" size=\"small\" [svgIcon]=\"icons.plus\"\n (click)=\"addMapping()\">Add</button>\n </div>\n\n @if (mappingsLoading()) {\n <div class=\"empty-hint\">Loading mappings\u2026</div>\n } @else if (mappingsError(); as err) {\n <div class=\"error-hint\">{{ err }}</div>\n } @else if (mappings().length === 0) {\n <div class=\"empty-hint\">\n No mappings reference this node yet. Add one using the button above.\n </div>\n } @else {\n <ul class=\"mapping-list\">\n @for (mapping of mappings(); track trackByRtId($index, mapping)) {\n <li class=\"mapping-row\" [class.mapping-disabled]=\"!mapping.enabled\">\n <div class=\"mapping-line\">\n <span class=\"mapping-name\">\n {{ mapping.name || 'Mapping ' + ($index + 1) }}\n </span>\n @if (!mapping.enabled) {\n <span class=\"mapping-badge mapping-badge-off\">disabled</span>\n }\n </div>\n <div class=\"mapping-details\">\n <span class=\"kv\">\n <span class=\"kv-key\">Source:</span>\n <span class=\"kv-val\">\n {{ mapping.sourceName || mapping.sourceRtId || '\u2014 not linked \u2014' }}\n </span>\n </span>\n @if (mapping.sourceAttributePath) {\n <span class=\"kv\">\n <span class=\"kv-key\">Attr:</span>\n <span class=\"kv-val\">{{ mapping.sourceAttributePath }}</span>\n </span>\n }\n @if (mapping.mappingExpression) {\n <span class=\"kv\">\n <span class=\"kv-key\">Expr:</span>\n <code class=\"kv-val\">{{ mapping.mappingExpression }}</code>\n </span>\n }\n @if (mapping.targetAttributePath) {\n <span class=\"kv\">\n <span class=\"kv-key\">\u2192</span>\n <span class=\"kv-val\">{{ mapping.targetAttributePath }}</span>\n </span>\n }\n </div>\n <div class=\"mapping-actions\">\n <button kendoButton themeColor=\"primary\" fillMode=\"flat\" size=\"small\"\n [svgIcon]=\"icons.pencil\"\n (click)=\"editMapping(mapping)\"\n title=\"Edit mapping\">Edit\u2026</button>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.trash\"\n (click)=\"deleteMapping(mapping)\"\n title=\"Delete mapping\"></button>\n </div>\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"empty-hint\">\n Select a node from the tree to view and edit its mappings.\n </div>\n }\n </div>\n </div>\n }\n\n @if (activeTab() === 'orphans') {\n <div class=\"coverage-toolbar\">\n <label class=\"toolbar-label\">Source type:</label>\n <select class=\"root-select\"\n [value]=\"orphanCkType() ?? ''\"\n (change)=\"onOrphanCkTypeChange($any($event.target).value)\">\n @for (ckType of config.sourceCandidateCkTypeIds; track ckType) {\n <option [value]=\"ckType\">{{ ckType }}</option>\n }\n </select>\n <button kendoButton fillMode=\"flat\" size=\"small\" [svgIcon]=\"icons.refresh\"\n [disabled]=\"!orphanCkType() || orphanLoading()\"\n (click)=\"refreshOrphans()\" title=\"Reload\"></button>\n <label class=\"orphan-toggle\">\n <input type=\"checkbox\" [checked]=\"orphanHideMapped()\" (change)=\"toggleOrphanHideMapped()\" />\n Hide already-mapped\n </label>\n <label class=\"toolbar-label\">Group by:</label>\n <select class=\"root-select\"\n [value]=\"orphanGroupParentType() ?? ''\"\n (change)=\"onOrphanGroupParentTypeChange($any($event.target).value)\">\n <option value=\"\">(no grouping)</option>\n @for (t of orphanAvailableParentTypes(); track t) {\n <option [value]=\"t\">{{ t }}</option>\n }\n </select>\n\n <div class=\"toolbar-spacer\"></div>\n\n @if (orphanStats(); as s) {\n <span class=\"validation-summary\">\n <span class=\"badge badge-error\" title=\"unmapped \u2014 no DataPointMapping yet\">{{ s.unmapped }} unmapped</span>\n <span class=\"badge badge-ok\" title=\"already covered by at least one mapping\">{{ s.mapped }} mapped</span>\n <span class=\"badge\">{{ s.total }} total</span>\n </span>\n }\n </div>\n\n <div class=\"orphan-body\">\n @if (orphanLoading()) {\n <div class=\"empty-hint\">Loading source candidates\u2026</div>\n } @else if (orphanError(); as err) {\n <div class=\"error-hint\">{{ err }}</div>\n } @else if (orphanFilteredList().length === 0) {\n <div class=\"empty-hint\">\n @if (orphanHideMapped()) {\n All {{ orphanCkType() }} entities are mapped at least once. \uD83C\uDF89\n } @else {\n No entities of type {{ orphanCkType() }} found.\n }\n </div>\n } @else if (orphanGroupParentType()) {\n @for (group of orphanGroupedList(); track group.key) {\n <div class=\"orphan-group\">\n <div class=\"orphan-group-header\">\n <span class=\"orphan-group-label\">{{ group.label }}</span>\n <span class=\"orphan-group-count\">{{ group.items.length }}</span>\n </div>\n <ul class=\"orphan-list\">\n @for (item of group.items; track trackOrphanByRtId($index, item)) {\n <ng-container *ngTemplateOutlet=\"orphanRowTpl; context: { $implicit: item }\"></ng-container>\n }\n </ul>\n </div>\n }\n } @else {\n <ul class=\"orphan-list\">\n @for (item of orphanFilteredList(); track trackOrphanByRtId($index, item)) {\n <ng-container *ngTemplateOutlet=\"orphanRowTpl; context: { $implicit: item }\"></ng-container>\n }\n </ul>\n }\n\n <ng-template #orphanRowTpl let-item>\n <li class=\"orphan-row\" [class.orphan-mapped]=\"item.mappingCount > 0\">\n <div class=\"orphan-line\">\n <span class=\"orphan-name\">{{ item.name }}</span>\n @if (item.mappingCount > 0) {\n <span class=\"orphan-badge orphan-badge-mapped\">\n {{ item.mappingCount }} mapping{{ item.mappingCount === 1 ? '' : 's' }}\n </span>\n } @else {\n <span class=\"orphan-badge orphan-badge-unmapped\">unmapped</span>\n }\n </div>\n @if (item.parentPath.length > 0) {\n <div class=\"orphan-breadcrumb\" [attr.title]=\"'Parent chain (root-first)'\">\n @for (parent of breadcrumbFor(item); track parent.rtId; let last = $last) {\n <span class=\"crumb\">{{ parent.name }}</span>\n @if (!last) { <span class=\"crumb-sep\">\u203A</span> }\n }\n </div>\n }\n <div class=\"orphan-meta\">\n <span class=\"kv-val\">{{ item.ckTypeId }}&#64;{{ item.rtId }}</span>\n </div>\n @if (item.description) {\n <div class=\"orphan-description\">{{ item.description }}</div>\n }\n <div class=\"orphan-actions\">\n <button kendoButton themeColor=\"primary\" fillMode=\"flat\" size=\"small\"\n [svgIcon]=\"icons.plus\"\n (click)=\"createMappingFromOrphan(item)\"\n title=\"Create mapping pre-linked to this source\">\n Map\u2026\n </button>\n </div>\n </li>\n </ng-template>\n </div>\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}.coverage-root{display:flex;flex-direction:column;flex:1;min-height:0;gap:12px}.coverage-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 12px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface-alt, #f8f9fa)) 70%,transparent)}.coverage-toolbar .toolbar-label{flex:0 0 96px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.coverage-toolbar .root-select{flex:0 0 280px;min-width:0;padding:4px 8px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:4px;background:var(--theme-bg-elevated, var(--kendo-color-surface, #fff));color:var(--theme-text-primary, inherit);font-size:.9rem;height:30px}.coverage-toolbar .toolbar-spacer{flex:1}.coverage-toolbar .toolbar-meta{display:flex;flex-direction:column;align-items:flex-end;line-height:1.1}.coverage-toolbar .toolbar-meta .meta-name{font-size:.85rem;font-weight:600}.coverage-toolbar .toolbar-meta .meta-type{font-size:.7rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d)}.coverage-body{flex:1;display:flex;gap:12px;min-height:0}.coverage-tree-pane{flex:0 0 40%;min-width:240px;display:flex;flex-direction:column;min-height:0;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);color:var(--theme-text-primary, inherit);overflow:auto}.coverage-detail-pane{flex:1;min-width:0;display:flex;flex-direction:column;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);color:var(--theme-text-primary, inherit);padding:12px 14px;gap:12px;overflow:auto}.detail-header{display:flex;align-items:flex-start;gap:8px}.detail-header .detail-titles{flex:1;min-width:0}.detail-header .detail-name{margin:0;font-size:1.05rem;font-weight:600;word-break:break-word}.detail-header .detail-subtitle{font-size:.72rem;font-family:monospace;color:var(--kendo-color-subtle, #6c757d);word-break:break-all}.detail-section-title{display:flex;align-items:center;gap:10px;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--kendo-color-subtle, #6c757d)}.detail-section-title .toolbar-spacer{flex:1}.detail-section-title .detail-summary{text-transform:none;letter-spacing:0;font-weight:500;color:var(--kendo-color-on-app-surface, inherit);font-size:.78rem}.mapping-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mapping-row{border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.mapping-row.mapping-disabled{opacity:.55}.mapping-line{display:flex;align-items:center;gap:8px}.mapping-line .mapping-name{font-weight:600;font-size:.9rem}.mapping-badge{font-size:.65rem;text-transform:uppercase;letter-spacing:.5px;padding:1px 6px;border-radius:8px;border:1px solid var(--kendo-color-border, #dee2e6)}.mapping-badge.mapping-badge-off{color:var(--kendo-color-subtle, #6c757d)}.mapping-details{display:flex;flex-wrap:wrap;gap:10px;font-size:.8rem}.mapping-details .kv{display:inline-flex;align-items:baseline;gap:4px}.mapping-details .kv-key{font-weight:600;color:var(--kendo-color-subtle, #6c757d)}.mapping-details code.kv-val{font-family:monospace;background:var(--kendo-color-surface, transparent);padding:1px 4px;border-radius:3px}.mapping-actions{display:flex;gap:6px;justify-content:flex-end}.empty-hint,.error-hint{padding:12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d);text-align:center}.error-hint{color:var(--kendo-color-error, #dc3545)}.coverage-tabs{display:flex;gap:0;border-bottom:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6))}.coverage-tab{padding:8px 16px;border:none;background:transparent;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-size:.85rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;display:inline-flex;align-items:center;gap:6px}.coverage-tab:hover{color:var(--theme-text-primary, inherit)}.coverage-tab.active{color:var(--theme-text-accent, var(--kendo-color-primary, #ff6358));border-bottom-color:var(--theme-text-accent, var(--kendo-color-primary, #ff6358))}.coverage-tab .tab-badge{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 22%,transparent);color:var(--kendo-color-error, #dc3545);border-radius:10px;padding:1px 8px;font-size:.7rem;font-weight:700}.orphan-toggle{display:inline-flex;align-items:center;gap:6px;font-size:.8rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));cursor:pointer}.orphan-body{flex:1;min-height:0;overflow:auto;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:6px;background:color-mix(in srgb,var(--theme-bg-surface, var(--kendo-color-surface, #fff)) 60%,transparent);padding:8px}.orphan-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.orphan-row{display:flex;flex-direction:column;gap:4px;padding:8px 10px;border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-left-width:3px;border-left-color:var(--kendo-color-error, #dc3545);border-radius:6px;background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f8f9fa)) 65%,transparent)}.orphan-row.orphan-mapped{border-left-color:var(--kendo-color-success, #28a745);opacity:.7}.orphan-row .orphan-line{display:flex;align-items:center;gap:8px}.orphan-row .orphan-line .orphan-name{flex:1;font-weight:600;font-size:.9rem}.orphan-row .orphan-badge{font-size:.7rem;padding:1px 8px;border-radius:10px;text-transform:uppercase;letter-spacing:.5px;font-weight:600}.orphan-row .orphan-badge-unmapped{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 22%,transparent);color:var(--kendo-color-error, #dc3545);border:1px solid var(--kendo-color-error, #dc3545)}.orphan-row .orphan-badge-mapped{background:color-mix(in srgb,var(--kendo-color-success, #28a745) 18%,transparent);color:var(--kendo-color-success, #28a745);border:1px solid var(--kendo-color-success, #28a745)}.orphan-row .orphan-meta{font-family:monospace;font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));word-break:break-all}.orphan-row .orphan-breadcrumb{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.75rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d))}.orphan-row .orphan-breadcrumb .crumb{line-height:1.3}.orphan-row .orphan-breadcrumb .crumb-sep{opacity:.55;font-weight:600}.orphan-row .orphan-description{font-size:.78rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));font-style:italic}.orphan-row .orphan-actions{display:flex;gap:6px;justify-content:flex-end}.orphan-group{display:flex;flex-direction:column;gap:6px}.orphan-group+.orphan-group{margin-top:14px}.orphan-group .orphan-group-header{display:flex;align-items:baseline;gap:8px;padding:4px 8px;border-bottom:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));font-size:.85rem;font-weight:600;color:var(--theme-text-primary, inherit)}.orphan-group .orphan-group-header .orphan-group-label{flex:1}.orphan-group .orphan-group-header .orphan-group-count{font-size:.7rem;color:var(--theme-text-secondary, var(--kendo-color-subtle, #6c757d));background:color-mix(in srgb,var(--theme-bg-elevated, var(--kendo-color-surface-alt, #f1f3f5)) 80%,transparent);border:1px solid var(--theme-border-subtle, var(--kendo-color-border, #dee2e6));border-radius:10px;padding:0 8px}.validation-summary{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}.badge{font-size:.7rem;padding:2px 8px;border-radius:10px;background:var(--kendo-color-surface, #f1f3f5);border:1px solid var(--kendo-color-border, #dee2e6)}.badge-error{background:color-mix(in srgb,var(--kendo-color-error, #dc3545) 18%,transparent);border-color:var(--kendo-color-error, #dc3545);color:var(--kendo-color-error, #dc3545);font-weight:600}.badge-warning{background:color-mix(in srgb,var(--kendo-color-warning, #ffc107) 22%,transparent);border-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-warning-on, #6b5500);font-weight:600}.badge-ok{background:color-mix(in srgb,var(--kendo-color-success, #28a745) 18%,transparent);border-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-success, #28a745);font-weight:600}.badge-info{background:color-mix(in srgb,var(--kendo-color-info, #0dcaf0) 18%,transparent);border-color:var(--kendo-color-info, #0dcaf0);color:var(--kendo-color-info, #0dcaf0)}.validation-error-text{color:var(--kendo-color-error, #dc3545);font-size:.8rem}.validation-timestamp{font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);margin-left:4px}\n"] }]
17392
+ }], propDecorators: { treeView: [{
17393
+ type: ViewChild,
17394
+ args: [TreeComponent, { static: false }]
17395
+ }], config: [{
17396
+ type: Input
17397
+ }], initialRoot: [{
17398
+ type: Input
17399
+ }], tenantId: [{
17400
+ type: Input
17401
+ }], entitySelected: [{
17402
+ type: Output
17403
+ }] } });
17404
+ function readAttr(items, name) {
17405
+ if (!items)
17406
+ return null;
17407
+ const target = name.toLowerCase();
17408
+ for (const item of items) {
17409
+ if (item?.attributeName != null && item.attributeName.toLowerCase() === target && item.value != null) {
17410
+ return String(item.value);
17411
+ }
17412
+ }
17413
+ return null;
17414
+ }
17415
+ /**
17416
+ * Parses the JSON report emitted by ValidateDataPointCoverage@1 into a lookup
17417
+ * map keyed by entity rtId, plus aggregate summary counters and a per-node
17418
+ * subtree rollup (worst status + aggregate counts) so the tree can colour
17419
+ * `info` ancestors red when there's an error somewhere below them.
17420
+ *
17421
+ * Tolerant of partial/malformed payloads: unknown statuses fall back to "info"
17422
+ * and missing arrays default to empty. Reports from an older backend that
17423
+ * doesn't emit `parentRtId` degrade gracefully — every node's subtreeStatus
17424
+ * collapses to its own status.
17425
+ */
17426
+ function parseValidationReport(serialised) {
17427
+ const result = new Map();
17428
+ const empty = { ok: 0, warning: 0, error: 0, info: 0, total: 0 };
17429
+ try {
17430
+ const parsed = JSON.parse(serialised);
17431
+ const summary = parsed?.summary ?? empty;
17432
+ const nodes = (parsed?.nodes ?? []).filter((n) => !!n?.rtId);
17433
+ // Index by rtId + build parent → children map for the rollup pass.
17434
+ const childrenByParent = new Map();
17435
+ const ownStatus = new Map();
17436
+ for (const n of nodes) {
17437
+ ownStatus.set(n.rtId, normaliseStatus(n.status));
17438
+ if (n.parentRtId) {
17439
+ const bucket = childrenByParent.get(n.parentRtId);
17440
+ if (bucket)
17441
+ bucket.push(n.rtId);
17442
+ else
17443
+ childrenByParent.set(n.parentRtId, [n.rtId]);
17444
+ }
17445
+ }
17446
+ // Memoised post-order walk: each node's subtree rollup includes itself
17447
+ // plus the recursive rollup of all descendants.
17448
+ const memo = new Map();
17449
+ const rollup = (rtId) => {
17450
+ const cached = memo.get(rtId);
17451
+ if (cached)
17452
+ return cached;
17453
+ const self = ownStatus.get(rtId) ?? 'info';
17454
+ const counts = { ok: 0, warning: 0, error: 0, info: 0 };
17455
+ counts[self] += 1;
17456
+ let worst = self;
17457
+ for (const childId of childrenByParent.get(rtId) ?? []) {
17458
+ const sub = rollup(childId);
17459
+ counts.ok += sub.counts.ok;
17460
+ counts.warning += sub.counts.warning;
17461
+ counts.error += sub.counts.error;
17462
+ counts.info += sub.counts.info;
17463
+ if (statusSeverity(sub.worst) > statusSeverity(worst))
17464
+ worst = sub.worst;
17465
+ }
17466
+ const out = { worst, counts };
17467
+ memo.set(rtId, out);
17468
+ return out;
17469
+ };
17470
+ for (const n of nodes) {
17471
+ const status = ownStatus.get(n.rtId) ?? 'info';
17472
+ const sub = rollup(n.rtId);
17473
+ result.set(n.rtId, {
17474
+ status,
17475
+ subtreeStatus: sub.worst,
17476
+ subtreeCounts: sub.counts,
17477
+ required: n.required ?? [],
17478
+ recommended: n.recommended ?? [],
17479
+ present: n.present ?? [],
17480
+ missingRequired: n.missingRequired ?? [],
17481
+ missingRecommended: n.missingRecommended ?? [],
17482
+ });
17483
+ }
17484
+ return { map: result, summary };
17485
+ }
17486
+ catch (err) {
17487
+ console.warn('Failed to parse validation report:', err);
17488
+ return { map: result, summary: empty };
17489
+ }
17490
+ }
17491
+ /**
17492
+ * Ordering used to compute "worst status" in a subtree rollup. Higher number
17493
+ * = more severe; ties are broken by the first occurrence in the traversal.
17494
+ */
17495
+ function statusSeverity(s) {
17496
+ switch (s) {
17497
+ case 'error': return 3;
17498
+ case 'warning': return 2;
17499
+ case 'ok': return 1;
17500
+ case 'info':
17501
+ default: return 0;
17502
+ }
17503
+ }
17504
+ function normaliseStatus(value) {
17505
+ switch (value) {
17506
+ case 'ok':
17507
+ case 'warning':
17508
+ case 'error':
17509
+ case 'info':
17510
+ return value;
17511
+ default:
17512
+ return 'info';
17513
+ }
17514
+ }
17515
+ function sleep(ms) {
17516
+ return new Promise(resolve => setTimeout(resolve, ms));
17517
+ }
17518
+ /**
17519
+ * Walks the up-to-3-hop parent chain produced by `getOrphanCandidates` and
17520
+ * flattens it into an array ordered from immediate parent (index 0) to
17521
+ * root-most known ancestor. Stops at the first hop with no parent items —
17522
+ * a Loxone Control whose Category is the topmost reachable ancestor in 3
17523
+ * hops yields a 1-item path; an OPC-UA node deep in a tree yields 3.
17524
+ */
17525
+ function extractParentPath(first) {
17526
+ const path = [];
17527
+ let cursor = first;
17528
+ while (cursor) {
17529
+ if (cursor.rtId == null || cursor.ckTypeId == null)
17530
+ break;
17531
+ path.push({
17532
+ rtId: String(cursor.rtId),
17533
+ ckTypeId: String(cursor.ckTypeId),
17534
+ name: readAttr(cursor.attributes?.items, 'name')
17535
+ ?? cursor.rtWellKnownName
17536
+ ?? String(cursor.rtId),
17537
+ });
17538
+ const next = cursor.associations?.parent?.items?.[0];
17539
+ cursor = next ?? null;
17540
+ }
17541
+ return path;
17542
+ }
17543
+
15210
17544
  class TenantSwitcherComponent {
15211
17545
  currentTenantId = null;
15212
17546
  allowedTenants = [];
@@ -15384,5 +17718,5 @@ function provideOctoUi() {
15384
17718
  * Generated bundle index. Do not edit.
15385
17719
  */
15386
17720
 
15387
- export { AssociationValidationService, AttributeSelectorDialogComponent, AttributeSelectorDialogService, AttributeSortSelectorDialogComponent, AttributeSortSelectorDialogService, AttributeValueTypeDto, CkTypeSelectorDialogComponent, CkTypeSelectorDialogService, CkTypeSelectorInputComponent, DEFAULT_RUNTIME_BROWSER_MESSAGES, DataMappingOverviewComponent, DefaultPropertyCategory, EntityDetailComponent, EntityIdInfoComponent, EntitySelectorDialogComponent, EntitySelectorDialogService, FieldFilterEditorComponent, OctoGraphQlDataSource, OctoGraphQlHierarchyDataSource, OctoLoaderComponent, PropertyConverterService, PropertyDisplayMode, PropertyGridComponent, PropertyValueDisplayComponent, RUNTIME_BROWSER_MESSAGES, RecordDetailDialogComponent, RtEntityIdHelper, RuntimeBrowserComponent, RuntimeBrowserOutletComponent, RuntimeBrowserPageComponent, RuntimeBrowserStateService, RuntimeEntityVariableDialogComponent, RuntimeEntityVariableDialogService, TenantSwitcherComponent, account_tree, add, analytics, app_registration, article, botService, category, chat, checklist, code, component_exchange, computer, createRuntimeBrowserRoutes, customer, dashboard, event_list, graphic_eq, group, identityService, insert_link, manage_accounts, more_time, notifications, page_info, pages, person_search, playlist_add_check, pool, power, provideOctoUi, publicIcon, query_builder, schedule_send, settings, sort, storage, swagger, swagger_asset, swagger_bot, swagger_communication, swagger_identity, team_dashboard, tenancy, text_snippet, travel_explore, user_diagnostics, webhook, work };
17721
+ export { AssociationValidationService, AttributeSelectorDialogComponent, AttributeSelectorDialogService, AttributeSortSelectorDialogComponent, AttributeSortSelectorDialogService, AttributeValueTypeDto, CkTypeSelectorDialogComponent, CkTypeSelectorDialogService, CkTypeSelectorInputComponent, DEFAULT_DATA_POINT, DEFAULT_MAPPING_COVERAGE_TREE_CONFIG, DEFAULT_RUNTIME_BROWSER_MESSAGES, DataMappingOverviewComponent, DataPointPickerComponent, DataPointResolverService, DefaultPropertyCategory, EntityDetailComponent, EntityIdInfoComponent, EntitySelectorDialogComponent, EntitySelectorDialogService, FieldFilterEditorComponent, MappingCoverageTreeComponent, MappingCoverageTreeDataSource, MappingEditDialogComponent, MappingEditDialogService, OctoGraphQlDataSource, OctoGraphQlHierarchyDataSource, OctoLoaderComponent, PropertyConverterService, PropertyDisplayMode, PropertyGridComponent, PropertyValueDisplayComponent, RUNTIME_BROWSER_MESSAGES, RecordDetailDialogComponent, RtEntityIdHelper, RuntimeBrowserComponent, RuntimeBrowserOutletComponent, RuntimeBrowserPageComponent, RuntimeBrowserStateService, RuntimeEntityVariableDialogComponent, RuntimeEntityVariableDialogService, TenantSwitcherComponent, account_tree, add, analytics, app_registration, article, botService, category, chat, checklist, code, component_exchange, computer, createRuntimeBrowserRoutes, customer, dashboard, event_list, extractDataPointNames, graphic_eq, group, identityService, insert_link, manage_accounts, more_time, notifications, page_info, pages, person_search, playlist_add_check, pool, power, provideOctoUi, publicIcon, query_builder, schedule_send, settings, sort, storage, swagger, swagger_asset, swagger_bot, swagger_communication, swagger_identity, team_dashboard, tenancy, text_snippet, travel_explore, user_diagnostics, webhook, work };
15388
17722
  //# sourceMappingURL=meshmakers-octo-ui.mjs.map