@acorex-platform/framework-client 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +7 -0
  2. package/auth/README.md +3 -0
  3. package/common/README.md +3 -0
  4. package/core/README.md +4 -0
  5. package/fesm2022/acorex-platform-framework-client-auth.mjs +1383 -0
  6. package/fesm2022/acorex-platform-framework-client-auth.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-CbzOYCnI.mjs +190 -0
  8. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-CbzOYCnI.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-framework-client-common.mjs +9467 -0
  10. package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-framework-client-core.mjs +4655 -0
  12. package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-framework-client-domain.mjs +3189 -0
  14. package/fesm2022/acorex-platform-framework-client-domain.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +3439 -0
  16. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CWVU3wXL.mjs +134 -0
  18. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CWVU3wXL.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-framework-client-layout-components.mjs +9777 -0
  20. package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs +2799 -0
  22. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-framework-client-layout-entity-attachments-page.component-CxBxxpmA.mjs +376 -0
  24. package/fesm2022/acorex-platform-framework-client-layout-entity-attachments-page.component-CxBxxpmA.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-framework-client-layout-entity-file-list-popup.component-l0JAHKIH.mjs +100 -0
  26. package/fesm2022/acorex-platform-framework-client-layout-entity-file-list-popup.component-l0JAHKIH.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +26299 -0
  28. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-framework-client-layout-views.mjs +2960 -0
  30. package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +4711 -0
  32. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-CpkWwygb.mjs +72 -0
  34. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-CpkWwygb.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-framework-client-layout-widgets-image-preview.popup-C_EPAvCU.mjs +29 -0
  36. package/fesm2022/acorex-platform-framework-client-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-CmtTq2Mt.mjs +172 -0
  38. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-CmtTq2Mt.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-DSx0E3NV.mjs +513 -0
  40. package/fesm2022/acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-DSx0E3NV.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-DdfcGV1C.mjs +274 -0
  42. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-DdfcGV1C.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs +64 -0
  44. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs.map +1 -0
  45. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-B5kSWxGk.mjs +34 -0
  46. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-B5kSWxGk.mjs.map +1 -0
  47. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +28553 -0
  48. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs.map +1 -0
  49. package/fesm2022/acorex-platform-framework-client-native.mjs +155 -0
  50. package/fesm2022/acorex-platform-framework-client-native.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-framework-client-runtime.mjs +911 -0
  52. package/fesm2022/acorex-platform-framework-client-runtime.mjs.map +1 -0
  53. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs +160 -0
  54. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs.map +1 -0
  55. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs +121 -0
  56. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs.map +1 -0
  57. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs +236 -0
  58. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs.map +1 -0
  59. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-D--_MIar.mjs +48 -0
  60. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-D--_MIar.mjs.map +1 -0
  61. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-Byyqk8pH.mjs +42 -0
  62. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-Byyqk8pH.mjs.map +1 -0
  63. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
  64. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
  65. package/fesm2022/acorex-platform-framework-client-themes-default.mjs +4160 -0
  66. package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -0
  67. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-column.component-CXSGiTCK.mjs +55 -0
  68. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-column.component-CXSGiTCK.mjs.map +1 -0
  69. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-view.component-BY5clQ1J.mjs +57 -0
  70. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-view.component-BY5clQ1J.mjs.map +1 -0
  71. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-DEL2YOTP.mjs +234 -0
  72. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-DEL2YOTP.mjs.map +1 -0
  73. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-column.component-BXl8u4Yp.mjs +94 -0
  74. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-column.component-BXl8u4Yp.mjs.map +1 -0
  75. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-view.component-i2sOOkVj.mjs +86 -0
  76. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-view.component-i2sOOkVj.mjs.map +1 -0
  77. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +2375 -0
  78. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -0
  79. package/fesm2022/acorex-platform-framework-client-workflow.mjs +2836 -0
  80. package/fesm2022/acorex-platform-framework-client-workflow.mjs.map +1 -0
  81. package/fesm2022/acorex-platform-framework-client.mjs +6 -0
  82. package/fesm2022/acorex-platform-framework-client.mjs.map +1 -0
  83. package/layout/builder/README.md +1578 -0
  84. package/layout/components/README.md +3 -0
  85. package/layout/designer/README.md +4 -0
  86. package/layout/entity/README.md +4 -0
  87. package/layout/views/README.md +3 -0
  88. package/layout/widget-core/README.md +4 -0
  89. package/layout/widgets/README.md +3 -0
  90. package/native/README.md +4 -0
  91. package/package.json +98 -0
  92. package/runtime/README.md +3 -0
  93. package/themes/default/README.md +3 -0
  94. package/themes/shared/README.md +3 -0
  95. package/types/acorex-platform-framework-client-auth.d.ts +690 -0
  96. package/types/acorex-platform-framework-client-common.d.ts +3287 -0
  97. package/types/acorex-platform-framework-client-core.d.ts +1849 -0
  98. package/types/acorex-platform-framework-client-domain.d.ts +1968 -0
  99. package/types/acorex-platform-framework-client-layout-builder.d.ts +1058 -0
  100. package/types/acorex-platform-framework-client-layout-components.d.ts +3362 -0
  101. package/types/acorex-platform-framework-client-layout-designer.d.ts +501 -0
  102. package/types/acorex-platform-framework-client-layout-entity.d.ts +4374 -0
  103. package/types/acorex-platform-framework-client-layout-views.d.ts +975 -0
  104. package/types/acorex-platform-framework-client-layout-widget-core.d.ts +1080 -0
  105. package/types/acorex-platform-framework-client-layout-widgets.d.ts +5263 -0
  106. package/types/acorex-platform-framework-client-native.d.ts +28 -0
  107. package/types/acorex-platform-framework-client-runtime.d.ts +301 -0
  108. package/types/acorex-platform-framework-client-themes-default.d.ts +889 -0
  109. package/types/acorex-platform-framework-client-themes-shared.d.ts +202 -0
  110. package/types/acorex-platform-framework-client-workflow.d.ts +1345 -0
  111. package/types/acorex-platform-framework-client.d.ts +2 -0
  112. package/workflow/README.md +4 -0
@@ -0,0 +1,4160 @@
1
+ import * as i1 from '@acorex/components/action-sheet';
2
+ import { AXActionSheetModule } from '@acorex/components/action-sheet';
3
+ import * as i1$2 from '@acorex/components/badge';
4
+ import { AXBadgeModule } from '@acorex/components/badge';
5
+ import { AXBreadcrumbsModule } from '@acorex/components/breadcrumbs';
6
+ import * as i3 from '@acorex/components/button';
7
+ import { AXButtonModule } from '@acorex/components/button';
8
+ import * as i7 from '@acorex/components/data-table';
9
+ import { AXDataTableModule } from '@acorex/components/data-table';
10
+ import * as i2$1 from '@acorex/components/decorators';
11
+ import { AXDecoratorModule } from '@acorex/components/decorators';
12
+ import { AXDialogModule } from '@acorex/components/dialog';
13
+ import * as i3$4 from '@acorex/components/drawer';
14
+ import { AXDrawerModule } from '@acorex/components/drawer';
15
+ import * as i5 from '@acorex/components/dropdown';
16
+ import { AXDropdownModule } from '@acorex/components/dropdown';
17
+ import { AXFormModule } from '@acorex/components/form';
18
+ import { AXLoadingModule } from '@acorex/components/loading';
19
+ import * as i3$2 from '@acorex/components/popover';
20
+ import { AXPopoverModule } from '@acorex/components/popover';
21
+ import * as i6 from '@acorex/components/search-box';
22
+ import { AXSearchBoxModule } from '@acorex/components/search-box';
23
+ import { AXTabsModule } from '@acorex/components/tabs';
24
+ import * as i2$3 from '@acorex/components/tooltip';
25
+ import { AXTooltipModule } from '@acorex/components/tooltip';
26
+ import * as i3$1 from '@acorex/core/translation';
27
+ import { AXTranslationModule, AXTranslationService, translateSync } from '@acorex/core/translation';
28
+ import * as i2 from '@acorex/core/utils';
29
+ import { AXUnsubscriber } from '@acorex/core/utils';
30
+ import { AXPAuthModule, AXPSessionService, AXPSessionStatus } from '@acorex-platform/framework-client/auth';
31
+ import { AXPRefreshEvent, AXPSettingsService, AXPCommonSettings, axpIsEntityListRoute, axpReplaceEntityListRouteQueryParams, axpIsEntityListPath, AXPTabNavigationService, AXPTabPreviewService, AXP_HOME_TAB_KEY, axpResolveTabTitleParts, AXP_PLATFORM_CONFIG_TOKEN, AXPMenuService, AXPMenuVisibilityService, axpMenuPathsMatch, AXPTabRouteCacheRegistry, axpResolveTabKeyFromSnapshot, axpHasTabScope, axpPathFromSnapshot, axpIsTabEligibleUrl, axpResolveTabKeyFromRouteConfig, AXP_PROTECTED_ROUTE_GUARDS, AXPCommonModule } from '@acorex-platform/framework-client/common';
32
+ import * as i1$5 from '@acorex-platform/framework-client/core';
33
+ import { AXPDeviceService, AXHighlightService, AXPGridLayoutDirective, AXPComponentSlotModule, AXPKeyboardShortcutRegistry, AXPBroadcastEventService, AXPContextStore } from '@acorex-platform/framework-client/core';
34
+ import { AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXPEntityDefinitionRegistryService, AXPCategoryTreeService, traceEntityMasterList, buildDataSourceTraceSnapshot, AXPEntityListViewColumnViewModel, AXPEntityMasterListCardSelectActionName, applyDataSourcePagingWithoutLoad, getDataSourcePageIndex, restoreEntityListExpandedRows, AXP_ENTITY_CONFIG_TOKEN } from '@acorex-platform/framework-client/layout/entity';
35
+ import * as i7$1 from '@acorex-platform/framework-client/layout/widget-core';
36
+ import { AXPWidgetCoreModule, AXPWidgetColumnCellComponent } from '@acorex-platform/framework-client/layout/widget-core';
37
+ import { AXPWidgetsModule } from '@acorex-platform/framework-client/layout/widgets';
38
+ import { AXPLayoutThemeService, AXPThemeLayoutSetting, AXPMenuVerticalMode, AXPThemesSharedModule } from '@acorex-platform/framework-client/themes/shared';
39
+ import { AXPWorkflowService, ofType } from '@acorex-platform/framework-client/workflow';
40
+ import * as i1$1 from '@angular/common';
41
+ import { CommonModule, AsyncPipe } from '@angular/common';
42
+ import * as i0 from '@angular/core';
43
+ import { inject, signal, computed, effect, ViewChild, Input, ChangeDetectionStrategy, ViewEncapsulation, Component, input, viewChild, afterNextRender, Injector, output, untracked, ElementRef, DestroyRef, NgModule } from '@angular/core';
44
+ import { FormsModule } from '@angular/forms';
45
+ import * as i1$6 from '@angular/router';
46
+ import { RouterModule, Router, ActivatedRoute, NavigationStart, NavigationEnd, RouteReuseStrategy, ROUTES } from '@angular/router';
47
+ import { Subject, takeUntil, filter, firstValueFrom } from 'rxjs';
48
+ import * as i1$4 from '@acorex/core/platform';
49
+ import { AXPThemeLayoutBlockComponent, AXPThemeLayoutHeaderComponent, AXPThemeLayoutToolbarComponent, AXPStateMessageComponent, AXPQueryFiltersComponent, AXPQuerySortsComponent, AXPQueryViewsComponent, AXPQueryColumnsComponent, AXPThemeLayoutStartSideComponent, AXPTaskBadgeService, AXPMenuBadgeHelper, AXPTaskBadgeDirective, AXPLogoComponent } from '@acorex-platform/framework-client/layout/components';
50
+ import { DragDropModule, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview } from '@angular/cdk/drag-drop';
51
+ import { AXDropdownButtonModule } from '@acorex/components/dropdown-button';
52
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
53
+ import * as i2$2 from '@acorex/components/skeleton';
54
+ import { AXSkeletonModule } from '@acorex/components/skeleton';
55
+ import { AXTreeViewModule, AXTreeViewComponent } from '@acorex/components/tree-view';
56
+ import { AXPCommandService } from '@acorex-platform/framework-client/runtime';
57
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
58
+ import { isEqual, get, sortBy } from 'lodash-es';
59
+ import * as i1$3 from '@acorex/components/data-pager';
60
+ import { AXDataPagerModule } from '@acorex/components/data-pager';
61
+ import * as i3$3 from '@acorex/components/check-box';
62
+ import { AXCheckBoxModule } from '@acorex/components/check-box';
63
+ import { AXPKeyboardShortcutPriority } from '@acorex-platform/framework-shared/core';
64
+ import * as i4 from '@acorex/components/menu';
65
+ import { AXContextMenuComponent, AXMenuModule } from '@acorex/components/menu';
66
+ import * as i2$4 from '@acorex/components/side-menu';
67
+ import { AXSideMenuModule } from '@acorex/components/side-menu';
68
+ import { AXAvatarModule } from '@acorex/components/avatar';
69
+ import { AXImageModule } from '@acorex/components/image';
70
+ import sortBy$1 from 'lodash-es/sortBy';
71
+ import { AXLoadingDialogService } from '@acorex/components/loading-dialog';
72
+ import { AXResizableDirective } from '@acorex/cdk/resizable';
73
+
74
+ class AXPEntityDetailListViewComponent {
75
+ constructor(actionSheetService, unsubscriber) {
76
+ this.actionSheetService = actionSheetService;
77
+ this.unsubscriber = unsubscriber;
78
+ this.workflow = inject(AXPWorkflowService);
79
+ this.store = inject(AXPLayoutThemeService);
80
+ this.deviceService = inject(AXPDeviceService);
81
+ this.toolbarPrimaryActions = signal([], ...(ngDevMode ? [{ debugName: "toolbarPrimaryActions" }] : /* istanbul ignore next */ []));
82
+ this.toolbarSecondaryActions = signal([], ...(ngDevMode ? [{ debugName: "toolbarSecondaryActions" }] : /* istanbul ignore next */ []));
83
+ this.destroyed = new Subject();
84
+ this.dropdownRowItems = computed(() => {
85
+ return this.vm.secondaryRowActions().map((c) => ({
86
+ icon: c.icon,
87
+ name: c.name,
88
+ text: c.title,
89
+ color: c.color,
90
+ }));
91
+ }, ...(ngDevMode ? [{ debugName: "dropdownRowItems" }] : /* istanbul ignore next */ []));
92
+ this.commandRowItems = computed(() => {
93
+ return this.vm.primaryRowActions().map((c) => {
94
+ return {
95
+ icon: c.icon,
96
+ name: c.name,
97
+ text: c.title,
98
+ color: c.color,
99
+ };
100
+ });
101
+ }, ...(ngDevMode ? [{ debugName: "commandRowItems" }] : /* istanbul ignore next */ []));
102
+ this.getCommandRowItems = () => {
103
+ return this.commandRowItems();
104
+ };
105
+ this.getDropdownRowItems = (rowData) => {
106
+ return Promise.resolve(this.dropdownRowItems());
107
+ };
108
+ effect(() => {
109
+ this.grid.selectedRows = this.vm.selectedItems();
110
+ // Re-evaluate toolbar actions whenever selection changes
111
+ this.refreshToolbarActions();
112
+ });
113
+ this.subscribeActionEvents();
114
+ }
115
+ handleChangeSearchValue(e) {
116
+ if (e.isUserInteraction) {
117
+ this.vm.applyInlineFilter(e.value);
118
+ }
119
+ }
120
+ subscribeActionEvents() {
121
+ this.workflow.events$
122
+ .pipe(ofType(AXPRefreshEvent))
123
+ .pipe(takeUntil(this.destroyed))
124
+ .subscribe((event) => {
125
+ console.log(event);
126
+ this.grid.refresh({
127
+ reset: false,
128
+ });
129
+ // Re-evaluate toolbar actions after refresh events
130
+ this.refreshToolbarActions();
131
+ });
132
+ }
133
+ ngAfterViewInit() {
134
+ // this.grid.refresh();
135
+ this.refreshToolbarActions();
136
+ }
137
+ async handleRowDbClick(e) {
138
+ const allItems = [...this.commandRowItems(), ...this.dropdownRowItems()];
139
+ // const defaultAction = allItems.find((c) => (c as any).default) || allItems[0];
140
+ const defaultAction = allItems.find((c) => c.default);
141
+ if (!defaultAction) {
142
+ return;
143
+ }
144
+ const d = {
145
+ component: e.component,
146
+ name: defaultAction.name,
147
+ data: e.data,
148
+ };
149
+ this.handleRowCommandClick(d);
150
+ }
151
+ async handleSelectedRowsChange(rows) {
152
+ this.vm.selectedItems.set(rows);
153
+ }
154
+ async handleRowCommandClick(e) {
155
+ this.vm.executeCommand(e.name, e.data);
156
+ }
157
+ async refreshToolbarActions() {
158
+ const [primary, secondary] = await Promise.all([this.vm.getPrimaryActions(), this.vm.getSecondaryActions()]);
159
+ this.toolbarPrimaryActions.set(primary);
160
+ this.toolbarSecondaryActions.set(secondary);
161
+ }
162
+ destroy() {
163
+ this.destroyed.next();
164
+ this.destroyed.complete();
165
+ }
166
+ ngOnDestroy() {
167
+ this.vm?.destroy();
168
+ this.destroy();
169
+ }
170
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailListViewComponent, deps: [{ token: i1.AXActionSheetService }, { token: i2.AXUnsubscriber }], target: i0.ɵɵFactoryTarget.Component }); }
171
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailListViewComponent, isStandalone: true, selector: "axp-entity-detail-list-view", inputs: { vm: ["viewModel", "vm"] }, providers: [AXUnsubscriber], viewQueries: [{ propertyName: "grid", first: true, predicate: ["grid"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"ax-flex ax-p-2\">\n <div class=\"ax-flex ax-flex-col ax-items-end ax-gap-3 ax-flex-1 ax-overflow-auto ax-min-h-72\">\n <div class=\"ax-flex ax-justify-between ax-items-center ax-w-full\">\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <div class=\"ax-flex ax-justify-start ax-items-center ax-gap-4\">\n @if (vm.hasSelectedItems()) {\n <div class=\"ax-flex ax-gap-3 ax-items-center ax-h-10\">\n <span class=\"ax-text-xl ax-font-bold\">\n {{ vm.selectedItems().length }}\n {{ '@general:terms.items-selected' | translate | async }}\n </span>\n <span\n (click)=\"vm.clearSelection()\"\n class=\"ax-text-sm ax-text-primary-500 ax-underline ax-cursor-pointer\"\n >{{ '@general:actions.unselect.title' | translate | async }}</span\n >\n </div>\n } @else {\n @if (vm.hasInlineFilters() && !deviceService.isSmall()) {\n <div class=\"ax-w-72\">\n <ax-search-box\n [placeholder]=\"\n ('@general:widgets.lookup.search' | translate | async) + vm.inlineFiltersPlaceholders().join(', ')\n \"\n (onValueChanged)=\"handleChangeSearchValue($event)\"\n ><ax-clear-button></ax-clear-button\n ></ax-search-box>\n </div>\n }\n }\n </div>\n </div>\n <div class=\"ax-flex ax-items-center ax-gap-3\">\n @for (tr of toolbarPrimaryActions(); track $index) {\n <ax-button\n [text]=\"(tr.title | translate | async)!\"\n [color]=\"tr.color\"\n [disabled]=\"tr.disabled\"\n (onClick)=\"vm.executeCommand(tr.name)\"\n >\n <ax-prefix>\n <ax-icon [icon]=\"tr.icon\"> </ax-icon>\n </ax-prefix>\n </ax-button>\n }\n @if (toolbarSecondaryActions().length) {\n <ax-button\n [text]=\"deviceService.isSmall() ? null : ('@general:terms.interface.actions' | translate | async)\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel>\n <ax-button-item-list>\n @for (tr of toolbarSecondaryActions(); track $index) {\n <ng-container>\n @if (tr.separated && !$first) {\n <ax-divider></ax-divider>\n }\n <ax-button-item\n [text]=\"(tr.title | translate | async)!\"\n class=\"ax-font-semibold ax-text-{{ tr.color }}\"\n (onClick)=\"vm.executeCommand(tr.name)\"\n >\n <ax-prefix>\n <ax-icon [icon]=\"tr.icon\"> </ax-icon>\n </ax-prefix>\n </ax-button-item>\n </ng-container>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n </div>\n </div>\n\n <div class=\"ax-flex ax-flex-1 ax-pt-0 ax-overflow-auto ax-max-w-full\">\n <ax-data-table\n #grid\n [showFooter]=\"false\"\n class=\"ax-flex-1\"\n [paging]=\"true\"\n [fetchDataMode]=\"'manual'\"\n [parentField]=\"vm.parentKey()\"\n [loading]=\"{ enabled: true, animation: true }\"\n [dataSource]=\"vm.dataSource\"\n (selectedRowsChange)=\"handleSelectedRowsChange($event)\"\n (onRowDbClick)=\"handleRowDbClick($event)\"\n >\n @if (vm.showIndexColumn()) {\n <ax-index-column\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm.selectedScopeActionsCount()) {\n <ax-select-column fixed=\"start\" [width]=\"'50px'\"></ax-select-column>\n }\n @for (col of vm.columns(); track col.name) {\n @if (col.visible) {\n <axp-widget-column-renderer\n [expandHandler]=\"$index === 0 && vm.parentKey() ? true : false\"\n [caption]=\"(col.title | translate | async)!\"\n [node]=\"col.node()\"\n ></axp-widget-column-renderer>\n }\n }\n @if (getCommandRowItems().length) {\n <ax-command-column\n fixed=\"end\"\n [width]=\"getCommandRowItems().length * 60 + 'px'\"\n [items]=\"getCommandRowItems()\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-command-column>\n }\n @if (dropdownRowItems().length) {\n <ax-dropdown-command-column\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"getDropdownRowItems\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-dropdown-command-column>\n }\n </ax-data-table>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i5.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "ngmodule", type: AXActionSheetModule }, { kind: "ngmodule", type: AXDrawerModule }, { kind: "ngmodule", type: AXDialogModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "ngmodule", type: AXBreadcrumbsModule }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i6.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i7.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i7.AXRowIndexColumnComponent, selector: "ax-index-column", inputs: ["width", "caption", "fixed", "footerTemplate", "padZero"] }, { kind: "component", type: i7.AXRowSelectColumnComponent, selector: "ax-select-column", inputs: ["width", "caption", "fixed"] }, { kind: "component", type: i7.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "component", type: i7.AXRowDropdownCommandColumnComponent, selector: "ax-dropdown-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "emptyStateTemplate", "emptyStateText", "items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type:
172
+ //
173
+ AXPWidgetCoreModule }, { kind: "component", type: i7$1.AXPWidgetColumnRendererComponent, selector: "axp-widget-column-renderer", inputs: ["caption", "customExpandIcon", "customCollapseIcon", "customWidth", "node", "footerTemplate", "expandHandler", "sortEnabled", "headerSortDirection", "headerSortPriority", "columnFixed", "cellTemplate", "headerTemplate"], outputs: ["sortToggle"] }, { kind: "ngmodule", type: AXPWidgetsModule }, { kind: "ngmodule", type: AXPAuthModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
174
+ }
175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailListViewComponent, decorators: [{
176
+ type: Component,
177
+ args: [{ selector: 'axp-entity-detail-list-view', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [AXUnsubscriber], imports: [
178
+ CommonModule,
179
+ FormsModule,
180
+ RouterModule,
181
+ AXButtonModule,
182
+ AXDecoratorModule,
183
+ AXBadgeModule,
184
+ AXDropdownModule,
185
+ AXPopoverModule,
186
+ AXFormModule,
187
+ AXActionSheetModule,
188
+ AXDrawerModule,
189
+ AXDialogModule,
190
+ AXLoadingModule,
191
+ AXTabsModule,
192
+ AXTooltipModule,
193
+ AXBreadcrumbsModule,
194
+ AXSearchBoxModule,
195
+ AXDataTableModule,
196
+ //
197
+ AXPWidgetCoreModule,
198
+ AXPWidgetsModule,
199
+ AXPAuthModule,
200
+ AXTranslationModule,
201
+ ], template: "<div class=\"ax-flex ax-p-2\">\n <div class=\"ax-flex ax-flex-col ax-items-end ax-gap-3 ax-flex-1 ax-overflow-auto ax-min-h-72\">\n <div class=\"ax-flex ax-justify-between ax-items-center ax-w-full\">\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <div class=\"ax-flex ax-justify-start ax-items-center ax-gap-4\">\n @if (vm.hasSelectedItems()) {\n <div class=\"ax-flex ax-gap-3 ax-items-center ax-h-10\">\n <span class=\"ax-text-xl ax-font-bold\">\n {{ vm.selectedItems().length }}\n {{ '@general:terms.items-selected' | translate | async }}\n </span>\n <span\n (click)=\"vm.clearSelection()\"\n class=\"ax-text-sm ax-text-primary-500 ax-underline ax-cursor-pointer\"\n >{{ '@general:actions.unselect.title' | translate | async }}</span\n >\n </div>\n } @else {\n @if (vm.hasInlineFilters() && !deviceService.isSmall()) {\n <div class=\"ax-w-72\">\n <ax-search-box\n [placeholder]=\"\n ('@general:widgets.lookup.search' | translate | async) + vm.inlineFiltersPlaceholders().join(', ')\n \"\n (onValueChanged)=\"handleChangeSearchValue($event)\"\n ><ax-clear-button></ax-clear-button\n ></ax-search-box>\n </div>\n }\n }\n </div>\n </div>\n <div class=\"ax-flex ax-items-center ax-gap-3\">\n @for (tr of toolbarPrimaryActions(); track $index) {\n <ax-button\n [text]=\"(tr.title | translate | async)!\"\n [color]=\"tr.color\"\n [disabled]=\"tr.disabled\"\n (onClick)=\"vm.executeCommand(tr.name)\"\n >\n <ax-prefix>\n <ax-icon [icon]=\"tr.icon\"> </ax-icon>\n </ax-prefix>\n </ax-button>\n }\n @if (toolbarSecondaryActions().length) {\n <ax-button\n [text]=\"deviceService.isSmall() ? null : ('@general:terms.interface.actions' | translate | async)\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel>\n <ax-button-item-list>\n @for (tr of toolbarSecondaryActions(); track $index) {\n <ng-container>\n @if (tr.separated && !$first) {\n <ax-divider></ax-divider>\n }\n <ax-button-item\n [text]=\"(tr.title | translate | async)!\"\n class=\"ax-font-semibold ax-text-{{ tr.color }}\"\n (onClick)=\"vm.executeCommand(tr.name)\"\n >\n <ax-prefix>\n <ax-icon [icon]=\"tr.icon\"> </ax-icon>\n </ax-prefix>\n </ax-button-item>\n </ng-container>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n </div>\n </div>\n\n <div class=\"ax-flex ax-flex-1 ax-pt-0 ax-overflow-auto ax-max-w-full\">\n <ax-data-table\n #grid\n [showFooter]=\"false\"\n class=\"ax-flex-1\"\n [paging]=\"true\"\n [fetchDataMode]=\"'manual'\"\n [parentField]=\"vm.parentKey()\"\n [loading]=\"{ enabled: true, animation: true }\"\n [dataSource]=\"vm.dataSource\"\n (selectedRowsChange)=\"handleSelectedRowsChange($event)\"\n (onRowDbClick)=\"handleRowDbClick($event)\"\n >\n @if (vm.showIndexColumn()) {\n <ax-index-column\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm.selectedScopeActionsCount()) {\n <ax-select-column fixed=\"start\" [width]=\"'50px'\"></ax-select-column>\n }\n @for (col of vm.columns(); track col.name) {\n @if (col.visible) {\n <axp-widget-column-renderer\n [expandHandler]=\"$index === 0 && vm.parentKey() ? true : false\"\n [caption]=\"(col.title | translate | async)!\"\n [node]=\"col.node()\"\n ></axp-widget-column-renderer>\n }\n }\n @if (getCommandRowItems().length) {\n <ax-command-column\n fixed=\"end\"\n [width]=\"getCommandRowItems().length * 60 + 'px'\"\n [items]=\"getCommandRowItems()\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-command-column>\n }\n @if (dropdownRowItems().length) {\n <ax-dropdown-command-column\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"getDropdownRowItems\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-dropdown-command-column>\n }\n </ax-data-table>\n </div>\n </div>\n</div>\n" }]
202
+ }], ctorParameters: () => [{ type: i1.AXActionSheetService }, { type: i2.AXUnsubscriber }], propDecorators: { vm: [{
203
+ type: Input,
204
+ args: ['viewModel']
205
+ }], grid: [{
206
+ type: ViewChild,
207
+ args: ['grid', { static: true }]
208
+ }] } });
209
+
210
+ //#region ---- Constants ----
211
+ const ROOT_NODE_ID = 'all';
212
+ const LOADING_DELAY_MS = 300;
213
+ const DEFAULT_TEXT_FIELD = 'title';
214
+ const DEFAULT_VALUE_FIELD = 'id';
215
+ //#endregion
216
+ class AXPEntityCategoryComponent {
217
+ //#endregion
218
+ //#region ---- Constructor & Lifecycle ----
219
+ constructor() {
220
+ //#region ---- Services & Dependencies ----
221
+ /** i18n key for the synthetic "all items" root row; resolved in the template with the translate pipe. */
222
+ this.categoryTreeRootTitleI18nKey = AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY;
223
+ this.translate = inject(AXTranslationService);
224
+ this.workflow = inject(AXPWorkflowService);
225
+ this.commandService = inject(AXPCommandService);
226
+ this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
227
+ this.categoryTreeService = inject(AXPCategoryTreeService);
228
+ this.highlightService = inject(AXHighlightService);
229
+ //#endregion
230
+ //#region ---- Component Inputs & View Queries ----
231
+ this.vm = input.required(...(ngDevMode ? [{ debugName: "vm" }] : /* istanbul ignore next */ []));
232
+ this.tree = viewChild('tree', ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
233
+ this.searchValue = input('', ...(ngDevMode ? [{ debugName: "searchValue" }] : /* istanbul ignore next */ []));
234
+ // Tree view inputs with defaults
235
+ this.selectMode = input('single', ...(ngDevMode ? [{ debugName: "selectMode" }] : /* istanbul ignore next */ []));
236
+ this.selectionBehavior = input('intermediate', ...(ngDevMode ? [{ debugName: "selectionBehavior" }] : /* istanbul ignore next */ []));
237
+ this.dragArea = input('handler', ...(ngDevMode ? [{ debugName: "dragArea" }] : /* istanbul ignore next */ []));
238
+ this.dragBehavior = input('none', ...(ngDevMode ? [{ debugName: "dragBehavior" }] : /* istanbul ignore next */ []));
239
+ this.showIcons = input(true, ...(ngDevMode ? [{ debugName: "showIcons" }] : /* istanbul ignore next */ []));
240
+ this.showChildrenBadge = input(false, ...(ngDevMode ? [{ debugName: "showChildrenBadge" }] : /* istanbul ignore next */ []));
241
+ this.expandedIcon = input('fa-solid fa-chevron-down', ...(ngDevMode ? [{ debugName: "expandedIcon" }] : /* istanbul ignore next */ []));
242
+ this.collapsedIcon = input('fa-solid fa-chevron-right', ...(ngDevMode ? [{ debugName: "collapsedIcon" }] : /* istanbul ignore next */ []));
243
+ this.indentSize = input(16, ...(ngDevMode ? [{ debugName: "indentSize" }] : /* istanbul ignore next */ []));
244
+ this.look = input('default', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
245
+ this.searchWithChildren = input(true, ...(ngDevMode ? [{ debugName: "searchWithChildren" }] : /* istanbul ignore next */ [])); // Include children of search results
246
+ //#endregion
247
+ //#region ---- Component State ----
248
+ this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
249
+ this.isSearching = signal(false, ...(ngDevMode ? [{ debugName: "isSearching" }] : /* istanbul ignore next */ []));
250
+ this.searchResultCount = signal(0, ...(ngDevMode ? [{ debugName: "searchResultCount" }] : /* istanbul ignore next */ []));
251
+ this.currentSearchValue = signal('', ...(ngDevMode ? [{ debugName: "currentSearchValue" }] : /* istanbul ignore next */ []));
252
+ this.resultsFoundText = signal('', ...(ngDevMode ? [{ debugName: "resultsFoundText" }] : /* istanbul ignore next */ []));
253
+ //#endregion
254
+ //#region ---- Private Properties ----
255
+ this.loadingTimeoutId = null;
256
+ this.treeData = null;
257
+ this.treeConfig = null;
258
+ this.matchingNodeIds = new Set();
259
+ this.relevantNodeIds = new Set(); // For search filtering
260
+ this.nodeDataCache = new Map(); // Cache parent data from search results
261
+ this.expandedNodesBeforeSearch = [];
262
+ this.nodesExpandedDuringSearch = [];
263
+ this.currentSearchTerm = null;
264
+ //#endregion
265
+ //#region ---- Computed Properties ----
266
+ this.textField = computed(() => this.vm().entityDef.category?.textField || DEFAULT_TEXT_FIELD, ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
267
+ this.valueField = computed(() => this.vm().entityDef.category?.valueField || DEFAULT_VALUE_FIELD, ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
268
+ /** When true, add/edit/delete category actions are hidden (from category plugin readonly option). */
269
+ this.categoryReadonly = computed(() => this.vm().entityDef?.extensions?.category?.readonly === true, ...(ngDevMode ? [{ debugName: "categoryReadonly" }] : /* istanbul ignore next */ []));
270
+ /**
271
+ * Computed property to check if we should show the "no search results" empty state.
272
+ * Returns true when search is active, not searching, and no results found.
273
+ */
274
+ this.showNoSearchResults = computed(() => {
275
+ const searchValue = this.currentSearchValue().trim();
276
+ const isSearching = this.isSearching();
277
+ const resultCount = this.searchResultCount();
278
+ return searchValue.length > 0 && !isSearching && resultCount === 0;
279
+ }, ...(ngDevMode ? [{ debugName: "showNoSearchResults" }] : /* istanbul ignore next */ []));
280
+ this.categoryEntityKey = computed(() => {
281
+ const key = this.vm().entityDef.category?.entity;
282
+ if (!key) {
283
+ throw new Error('Category entity is not configured');
284
+ }
285
+ return key;
286
+ }, ...(ngDevMode ? [{ debugName: "categoryEntityKey" }] : /* istanbul ignore next */ []));
287
+ // protected toggleExpand(e: AXTreeViewNodeDoubleClickEvent) {
288
+ // (e.component as AXTreeViewComponent).toggleNodeExpansion(String(e.node[this.valueField()] ?? ''));
289
+ // }
290
+ //#endregion
291
+ //#region ---- Tree Data Source ----
292
+ /**
293
+ * Datasource callback for tree-view component.
294
+ * Provides lazy loading support for tree nodes.
295
+ */
296
+ this.datasource = async (parentId) => {
297
+ if (!this.isTreeInitialized()) {
298
+ return [];
299
+ }
300
+ // Load root nodes if no parent ID provided
301
+ if (!parentId) {
302
+ return await this.loadRootNodes();
303
+ }
304
+ // Load children for the specified parent
305
+ return await this.loadChildNodes(parentId);
306
+ };
307
+ afterNextRender(() => {
308
+ this.initializeTree();
309
+ });
310
+ this.workflow.events$
311
+ .pipe(ofType(AXPRefreshEvent))
312
+ .pipe(takeUntilDestroyed())
313
+ .subscribe((event) => {
314
+ this.tree()?.reloadData();
315
+ // this.tree()?.refresh();
316
+ });
317
+ }
318
+ //#endregion
319
+ //#region ---- Public Methods ----
320
+ /**
321
+ * Handles category search input changes - Uses server-side search for efficiency and reliability
322
+ */
323
+ async handleCategorySearchChange(event) {
324
+ if (!this.isTreeInitialized()) {
325
+ return;
326
+ }
327
+ const searchTerm = (event.value ?? '').trim().toLowerCase();
328
+ const previousSearchTerm = this.currentSearchValue();
329
+ // If search is cleared, reset tree
330
+ if (!searchTerm) {
331
+ if (previousSearchTerm || this.matchingNodeIds.size > 0) {
332
+ this.currentSearchValue.set('');
333
+ this.currentSearchTerm = null;
334
+ await this.resetSearch();
335
+ }
336
+ return;
337
+ }
338
+ // For non-empty search, only process user interactions
339
+ if (!event.isUserInteraction) {
340
+ return;
341
+ }
342
+ // Prevent concurrent searches
343
+ if (this.isSearching() && this.currentSearchTerm !== null) {
344
+ if (this.currentSearchTerm === searchTerm) {
345
+ return;
346
+ }
347
+ }
348
+ this.currentSearchValue.set(event.value ?? '');
349
+ this.currentSearchTerm = searchTerm;
350
+ const treeComponent = this.tree();
351
+ if (!treeComponent) {
352
+ return;
353
+ }
354
+ // Store expanded nodes before starting a new search (only on first search)
355
+ if (!previousSearchTerm && this.expandedNodesBeforeSearch.length === 0) {
356
+ const expandedNodes = treeComponent.getExpandedNodes();
357
+ this.expandedNodesBeforeSearch = expandedNodes
358
+ .map((node) => String(node['id'] ?? ''))
359
+ .filter((id) => id && id !== ROOT_NODE_ID);
360
+ }
361
+ // Collapse nodes from previous search before starting new search
362
+ if (previousSearchTerm && this.nodesExpandedDuringSearch.length > 0) {
363
+ const nodesToCollapse = [...this.nodesExpandedDuringSearch].reverse();
364
+ this.nodesExpandedDuringSearch = [];
365
+ for (const nodeId of nodesToCollapse) {
366
+ // Only collapse if it wasn't originally expanded before search
367
+ if (!this.expandedNodesBeforeSearch.includes(nodeId)) {
368
+ try {
369
+ treeComponent.collapseNode(nodeId);
370
+ }
371
+ catch {
372
+ // Node might not exist anymore, ignore
373
+ }
374
+ }
375
+ }
376
+ }
377
+ this.isSearching.set(true);
378
+ try {
379
+ // Step 1: Use server-side search to get matching items
380
+ const searchResults = await this.categoryTreeService.searchCategories(searchTerm, this.treeData, this.treeConfig);
381
+ // Check if search term changed during the API call
382
+ if (this.currentSearchTerm !== searchTerm) {
383
+ return;
384
+ }
385
+ if (!searchResults || searchResults.length === 0) {
386
+ this.matchingNodeIds.clear();
387
+ this.relevantNodeIds.clear();
388
+ this.searchResultCount.set(0);
389
+ await this.updateTranslatedMessages(0);
390
+ // Clear highlighting and reload tree to show all nodes
391
+ this.highlightService.clear();
392
+ await treeComponent.reloadData();
393
+ return;
394
+ }
395
+ // Store matching node IDs from search results
396
+ this.matchingNodeIds.clear();
397
+ const valueField = this.valueField();
398
+ searchResults.forEach((item) => {
399
+ const nodeId = String(item[valueField] ?? '');
400
+ if (nodeId) {
401
+ this.matchingNodeIds.add(nodeId);
402
+ }
403
+ });
404
+ const resultCount = searchResults.length;
405
+ this.searchResultCount.set(resultCount);
406
+ // Update translated messages
407
+ await this.updateTranslatedMessages(resultCount);
408
+ // Step 2: Collect parent IDs (builds relevantNodeIds for filtering)
409
+ const parentsToExpand = await this.collectParentIds(searchResults);
410
+ // Step 2.5: Collect children IDs if searchWithChildren is enabled
411
+ if (this.searchWithChildren()) {
412
+ await this.collectChildrenIds(searchResults);
413
+ }
414
+ // Step 3: Reload tree to apply filtering
415
+ await treeComponent.reloadData();
416
+ // Step 4: Expand root node
417
+ if (!treeComponent.isNodeExpanded(ROOT_NODE_ID)) {
418
+ try {
419
+ await treeComponent.expandNode(ROOT_NODE_ID);
420
+ this.nodesExpandedDuringSearch.push(ROOT_NODE_ID);
421
+ }
422
+ catch {
423
+ // Root might not exist
424
+ }
425
+ }
426
+ // Step 4: Expand all parent nodes in order (from root to leaves)
427
+ // Sort parents by their depth to expand in correct order
428
+ const sortedParents = await this.sortParentsByDepth(Array.from(parentsToExpand));
429
+ for (const parentId of sortedParents) {
430
+ // Only expand if not already expanded
431
+ if (!treeComponent.isNodeExpanded(parentId)) {
432
+ try {
433
+ await treeComponent.expandNode(parentId);
434
+ this.nodesExpandedDuringSearch.push(parentId);
435
+ // Small delay to prevent overwhelming the tree component
436
+ await new Promise((resolve) => setTimeout(resolve, 10));
437
+ }
438
+ catch {
439
+ // Node might not exist, ignore
440
+ }
441
+ }
442
+ }
443
+ // Step 5: Apply highlighting after tree is rendered
444
+ // Use setTimeout to ensure DOM is updated after tree reload
445
+ setTimeout(() => {
446
+ if (this.currentSearchValue().trim()) {
447
+ this.highlightService.highlight('ax-tree-view .ax-truncate', this.currentSearchValue().trim());
448
+ }
449
+ }, 100);
450
+ }
451
+ catch (error) {
452
+ console.error('Error searching categories:', error);
453
+ this.matchingNodeIds.clear();
454
+ this.relevantNodeIds.clear();
455
+ this.searchResultCount.set(0);
456
+ this.highlightService.clear();
457
+ // Reload tree to clear any filters
458
+ const treeComponent = this.tree();
459
+ if (treeComponent) {
460
+ await treeComponent.reloadData();
461
+ }
462
+ }
463
+ finally {
464
+ if (this.currentSearchTerm === searchTerm) {
465
+ this.currentSearchTerm = null;
466
+ }
467
+ this.isSearching.set(false);
468
+ }
469
+ }
470
+ /**
471
+ * Collects all parent IDs for the given search results
472
+ * Handles both nested 'parent' object and 'parentId' field in search results
473
+ * Recursively fetches parent nodes to build the full parent chain
474
+ */
475
+ async collectParentIds(items) {
476
+ const parentsToExpand = new Set();
477
+ const valueField = this.valueField();
478
+ const parentKey = this.treeData?.categoryEntityDef?.parentKey || 'parentId';
479
+ // Build relevantNodeIds = matching nodes + all parents
480
+ this.relevantNodeIds.clear();
481
+ this.matchingNodeIds.forEach((id) => this.relevantNodeIds.add(id));
482
+ // Recursively extract all parent IDs
483
+ const extractParentIds = async (item) => {
484
+ // First, try to get parent from nested 'parent' object (if available)
485
+ const parent = item['parent'];
486
+ if (parent) {
487
+ const parentId = String(parent[valueField] ?? parent['id'] ?? '');
488
+ if (parentId && parentId !== ROOT_NODE_ID) {
489
+ parentsToExpand.add(parentId);
490
+ this.relevantNodeIds.add(parentId); // Add to filter set
491
+ this.nodeDataCache.set(parentId, parent);
492
+ await extractParentIds(parent);
493
+ }
494
+ }
495
+ else {
496
+ // Fallback: use parentId field and fetch parent from server
497
+ // Try both parentKey from entity definition and common 'parentId' field
498
+ const parentIdValue = item[parentKey] ?? item['parentId'];
499
+ if (parentIdValue) {
500
+ const parentId = String(parentIdValue);
501
+ if (parentId && parentId !== ROOT_NODE_ID) {
502
+ // Add to sets first
503
+ parentsToExpand.add(parentId);
504
+ this.relevantNodeIds.add(parentId);
505
+ // Fetch parent from server if not already cached
506
+ if (!this.nodeDataCache.has(parentId)) {
507
+ const parentItem = await this.fetchItemById(parentId);
508
+ if (parentItem) {
509
+ this.nodeDataCache.set(parentId, parentItem);
510
+ // Recursively fetch parent's parent
511
+ await extractParentIds(parentItem);
512
+ }
513
+ }
514
+ else {
515
+ // Parent already in cache, recursively process it
516
+ const cachedParent = this.nodeDataCache.get(parentId);
517
+ if (cachedParent) {
518
+ await extractParentIds(cachedParent);
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ };
525
+ // Process all items
526
+ for (const item of items) {
527
+ await extractParentIds(item);
528
+ }
529
+ return parentsToExpand;
530
+ }
531
+ /**
532
+ * Collects children IDs for matching search results recursively
533
+ * Adds all descendants (children, grandchildren, etc.) to relevantNodeIds so they appear in the filtered tree
534
+ */
535
+ async collectChildrenIds(items) {
536
+ if (!this.treeData?.categoryEntityQueryFunc || !this.treeConfig) {
537
+ return;
538
+ }
539
+ const valueField = this.valueField();
540
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
541
+ if (!parentKey) {
542
+ return;
543
+ }
544
+ // For each matching node, recursively fetch all its descendants
545
+ for (const item of items) {
546
+ const nodeId = String(item[valueField] ?? '');
547
+ if (!nodeId || nodeId === ROOT_NODE_ID) {
548
+ continue;
549
+ }
550
+ await this.collectChildrenRecursively(nodeId, valueField, parentKey);
551
+ }
552
+ }
553
+ /**
554
+ * Recursively collects all descendant IDs for a given node
555
+ */
556
+ async collectChildrenRecursively(nodeId, valueField, parentKey) {
557
+ if (!this.treeData?.categoryEntityQueryFunc) {
558
+ return;
559
+ }
560
+ try {
561
+ // Fetch children for this node
562
+ const event = {
563
+ ...this.treeData.basicQueryEvent,
564
+ filter: {
565
+ field: parentKey,
566
+ value: nodeId,
567
+ operator: { type: 'equal' },
568
+ },
569
+ };
570
+ const res = await this.treeData.categoryEntityQueryFunc(event);
571
+ if (res?.items && res.items.length > 0) {
572
+ // Add children IDs to relevantNodeIds so they appear in filtered tree
573
+ for (const child of res.items) {
574
+ const childId = String(child[valueField] ?? '');
575
+ if (childId && childId !== ROOT_NODE_ID) {
576
+ this.relevantNodeIds.add(childId);
577
+ // Cache child data
578
+ this.nodeDataCache.set(childId, child);
579
+ // Recursively fetch children of this child
580
+ await this.collectChildrenRecursively(childId, valueField, parentKey);
581
+ }
582
+ }
583
+ }
584
+ }
585
+ catch (error) {
586
+ console.error(`Error fetching children for node ${nodeId}:`, error);
587
+ }
588
+ }
589
+ /**
590
+ * Fetches a single item by ID from the server
591
+ */
592
+ async fetchItemById(id) {
593
+ if (!this.treeData?.categoryEntityQueryFunc) {
594
+ return null;
595
+ }
596
+ try {
597
+ const valueField = this.valueField();
598
+ const event = {
599
+ ...this.treeData.basicQueryEvent,
600
+ filter: {
601
+ field: valueField,
602
+ value: id,
603
+ operator: { type: 'equal' },
604
+ },
605
+ };
606
+ const res = await this.treeData.categoryEntityQueryFunc(event);
607
+ return res?.items?.[0] ?? null;
608
+ }
609
+ catch (error) {
610
+ console.error('Error fetching item by ID:', error);
611
+ return null;
612
+ }
613
+ }
614
+ /**
615
+ * Sorts parent IDs by their depth (root first, leaves last)
616
+ * Uses cached data from collectParentIds
617
+ */
618
+ async sortParentsByDepth(parentIds) {
619
+ const parentKey = this.treeData?.categoryEntityDef?.parentKey;
620
+ if (!parentKey || parentIds.length === 0) {
621
+ return parentIds;
622
+ }
623
+ // Build depth map using cached data
624
+ const depthMap = new Map();
625
+ for (const parentId of parentIds) {
626
+ if (depthMap.has(parentId)) {
627
+ continue;
628
+ }
629
+ let depth = 0;
630
+ let currentId = parentId;
631
+ const visited = new Set();
632
+ while (currentId && currentId !== ROOT_NODE_ID && !visited.has(currentId)) {
633
+ visited.add(currentId);
634
+ depth++;
635
+ // Use nodeDataCache which was populated by collectParentIds
636
+ const item = this.nodeDataCache.get(currentId);
637
+ if (item) {
638
+ currentId = item[parentKey] ? String(item[parentKey]) : null;
639
+ }
640
+ else {
641
+ break;
642
+ }
643
+ }
644
+ depthMap.set(parentId, depth);
645
+ }
646
+ // Sort by depth (lower depth = closer to root = expand first)
647
+ return parentIds.sort((a, b) => {
648
+ const depthA = depthMap.get(a) ?? 0;
649
+ const depthB = depthMap.get(b) ?? 0;
650
+ return depthA - depthB;
651
+ });
652
+ }
653
+ /**
654
+ * Updates translated messages for search results
655
+ */
656
+ async updateTranslatedMessages(resultCount) {
657
+ if (resultCount > 0) {
658
+ const key = resultCount === 1
659
+ ? '@general:terms.interface.category.search.results-found.singular'
660
+ : '@general:terms.interface.category.search.results-found.plural';
661
+ const text = await this.translate.translateAsync(key, { params: { count: resultCount } });
662
+ this.resultsFoundText.set(text);
663
+ }
664
+ else {
665
+ this.resultsFoundText.set('');
666
+ }
667
+ }
668
+ /**
669
+ * Resets search state and restores tree to original expanded state
670
+ */
671
+ async resetSearch() {
672
+ this.searchResultCount.set(0);
673
+ this.resultsFoundText.set('');
674
+ this.currentSearchTerm = null;
675
+ this.isSearching.set(false);
676
+ this.matchingNodeIds.clear();
677
+ this.relevantNodeIds.clear();
678
+ this.nodeDataCache.clear();
679
+ // Clear highlighting
680
+ this.highlightService.clear();
681
+ const treeComponent = this.tree();
682
+ if (!treeComponent) {
683
+ this.expandedNodesBeforeSearch = [];
684
+ this.nodesExpandedDuringSearch = [];
685
+ return;
686
+ }
687
+ // Reload tree to show all nodes (no filtering)
688
+ await treeComponent.reloadData();
689
+ // Collapse nodes that were expanded during search (in reverse order - leaves first)
690
+ const nodesToCollapse = [...this.nodesExpandedDuringSearch].reverse();
691
+ this.nodesExpandedDuringSearch = [];
692
+ for (const nodeId of nodesToCollapse) {
693
+ // Only collapse if it wasn't originally expanded before search
694
+ if (!this.expandedNodesBeforeSearch.includes(nodeId)) {
695
+ try {
696
+ treeComponent.collapseNode(nodeId);
697
+ }
698
+ catch {
699
+ // Node might not exist anymore, ignore
700
+ }
701
+ }
702
+ }
703
+ // Clear the stored expanded nodes
704
+ this.expandedNodesBeforeSearch = [];
705
+ }
706
+ /**
707
+ * Checks if a node matches the current search term
708
+ */
709
+ isMatchingNode(nodeId) {
710
+ return this.matchingNodeIds.has(nodeId);
711
+ }
712
+ /**
713
+ * Handles node click events to apply category filters and track selected category for create command
714
+ */
715
+ handleNodeClick(node) {
716
+ const nodeData = this.extractNodeData(node);
717
+ const applyConditions = this.vm().entityDef.category?.applyConditions || [];
718
+ const valueField = this.valueField();
719
+ const textField = this.textField();
720
+ const nodeId = String(nodeData[valueField] ?? node['id'] ?? '');
721
+ // Track selected category for default category when creating new entities
722
+ if (nodeId === 'all' || !nodeId) {
723
+ this.vm().setSelectedCategory(null);
724
+ }
725
+ else {
726
+ this.vm().setSelectedCategory({
727
+ id: nodeId,
728
+ title: String(nodeData[textField] ?? node['title'] ?? ''),
729
+ });
730
+ }
731
+ const categoryFilters = this.buildCategoryFilters(nodeData, applyConditions);
732
+ const viewFilters = this.buildViewFilters();
733
+ this.vm().dataSource.filter({
734
+ filters: [...viewFilters, ...categoryFilters],
735
+ logic: 'and',
736
+ });
737
+ this.vm().dataSource.refresh();
738
+ }
739
+ /**
740
+ * Handles node toggle events (expansion/collapse)
741
+ */
742
+ async onNodeToggle(_event) {
743
+ // Tree component handles lazy loading via datasource callback
744
+ }
745
+ //#endregion
746
+ //#region ---- CRUD Operations ----
747
+ /**
748
+ * Creates a new root category node (uses Entity:Create command).
749
+ */
750
+ async handleCreateRootClick(event) {
751
+ this.preventDefaultAndStopPropagation(event);
752
+ try {
753
+ const result = await this.executeCreateCommand(undefined);
754
+ await this.applyCreateResult(result, undefined);
755
+ }
756
+ catch (error) {
757
+ console.error('Error creating root category:', error);
758
+ }
759
+ }
760
+ /**
761
+ * Creates a new child category under the given parent node (uses Entity:Create command).
762
+ */
763
+ async handleCreateChildClick(node, event) {
764
+ this.preventDefaultAndStopPropagation(event);
765
+ try {
766
+ const parentId = this.extractNodeId(node);
767
+ const result = await this.executeCreateCommand(parentId);
768
+ await this.applyCreateResult(result, parentId);
769
+ }
770
+ catch (error) {
771
+ console.error('Error creating child category:', error);
772
+ }
773
+ }
774
+ /**
775
+ * Updates an existing category node (uses Entity:Update command).
776
+ */
777
+ async handleEditNodeClick(node, event) {
778
+ this.preventDefaultAndStopPropagation(event);
779
+ try {
780
+ const nodeData = this.extractNodeData(node);
781
+ const result = await this.executeUpdateCommand(nodeData);
782
+ await this.applyUpdateResult(result, node);
783
+ }
784
+ catch (error) {
785
+ console.error('Error editing category:', error);
786
+ }
787
+ }
788
+ /**
789
+ * Deletes a category node
790
+ */
791
+ async handleDeleteNodeClick(node, event) {
792
+ this.preventDefaultAndStopPropagation(event);
793
+ try {
794
+ const nodeData = this.extractNodeData(node);
795
+ const context = await this.executeDeleteWorkflow(nodeData);
796
+ await this.handleDeleteResult(context, node);
797
+ }
798
+ catch (error) {
799
+ console.error('Error deleting category:', error);
800
+ }
801
+ }
802
+ //#endregion
803
+ //#region ---- Private Tree Management Methods ----
804
+ /**
805
+ * Initializes the category tree data structure
806
+ */
807
+ async initializeTree() {
808
+ this.setLoadingWithDelay(true);
809
+ try {
810
+ const entityKey = this.categoryEntityKey();
811
+ this.treeConfig = {
812
+ entityKey,
813
+ textField: this.textField(),
814
+ valueField: this.valueField(),
815
+ };
816
+ this.treeData = await this.categoryTreeService.initializeCategoryTree(this.treeConfig);
817
+ if (!this.treeData) {
818
+ this.clearLoadingState();
819
+ return;
820
+ }
821
+ // Set parent key from entity definition
822
+ if (this.treeData.categoryEntityDef?.parentKey) {
823
+ this.treeConfig.parentKey = this.treeData.categoryEntityDef.parentKey;
824
+ }
825
+ }
826
+ catch (error) {
827
+ console.error('Error loading categories:', error);
828
+ }
829
+ finally {
830
+ this.clearLoadingState();
831
+ }
832
+ }
833
+ /**
834
+ * Loads root nodes for the tree
835
+ */
836
+ async loadRootNodes() {
837
+ if (!this.isTreeInitialized()) {
838
+ return [];
839
+ }
840
+ // Load root categories
841
+ const items = await this.categoryTreeService.loadRootCategories(this.treeData, this.treeConfig);
842
+ if (!items) {
843
+ return [];
844
+ }
845
+ const rootNode = await this.categoryTreeService.createRootNode(items, this.treeConfig);
846
+ // Filter root's children when search is active
847
+ if (this.relevantNodeIds.size > 0 && rootNode['children']) {
848
+ rootNode['children'] = rootNode['children'].filter((child) => {
849
+ const childId = String(child['id'] ?? '');
850
+ return this.relevantNodeIds.has(childId);
851
+ });
852
+ rootNode['childrenCount'] = rootNode['children'].length;
853
+ }
854
+ return [rootNode];
855
+ }
856
+ /**
857
+ * Loads child nodes for a given parent ID
858
+ */
859
+ async loadChildNodes(parentId) {
860
+ if (!this.isTreeInitialized()) {
861
+ return [];
862
+ }
863
+ // Create minimal node object - loadChildren only needs node.id
864
+ const targetNode = {
865
+ id: parentId,
866
+ };
867
+ let children = await this.categoryTreeService.loadChildren(targetNode, this.treeData, this.treeConfig);
868
+ // Filter children when search is active
869
+ if (this.relevantNodeIds.size > 0) {
870
+ children = children.filter((child) => {
871
+ const childId = String(child['id'] ?? '');
872
+ return this.relevantNodeIds.has(childId);
873
+ });
874
+ }
875
+ return children;
876
+ }
877
+ /**
878
+ * Reloads tree data from the server and optionally expands a parent node.
879
+ */
880
+ async refreshAfterChange(parentId) {
881
+ const treeComponent = this.tree();
882
+ if (!treeComponent) {
883
+ return;
884
+ }
885
+ await treeComponent.reloadData();
886
+ const expandId = parentId ?? ROOT_NODE_ID;
887
+ try {
888
+ if (!treeComponent.isNodeExpanded(expandId)) {
889
+ await treeComponent.expandNode(expandId);
890
+ }
891
+ }
892
+ catch {
893
+ // Parent node may not exist yet after reload; ignore.
894
+ }
895
+ }
896
+ /**
897
+ * Adds a new node to the tree after create operation
898
+ */
899
+ async addNodeToTree(entityData, parentId) {
900
+ if (!this.isTreeInitialized()) {
901
+ return;
902
+ }
903
+ const treeComponent = this.tree();
904
+ if (!treeComponent) {
905
+ return;
906
+ }
907
+ const newNode = this.categoryTreeService.convertToTreeNode(entityData, this.treeConfig);
908
+ if (parentId) {
909
+ await this.addChildNode(treeComponent, parentId, newNode);
910
+ }
911
+ else {
912
+ await this.addRootNode(treeComponent, newNode);
913
+ }
914
+ }
915
+ /**
916
+ * Adds a child node to a parent node
917
+ */
918
+ async addChildNode(treeComponent, parentId, newNode) {
919
+ const parentNode = treeComponent.findNode(parentId);
920
+ if (!parentNode) {
921
+ await this.refreshAfterChange(parentId);
922
+ return;
923
+ }
924
+ if (!treeComponent.isNodeExpanded(parentId)) {
925
+ await treeComponent.expandNode(parentId);
926
+ }
927
+ treeComponent.addChild(parentId, newNode);
928
+ }
929
+ /**
930
+ * Adds a root node to the tree
931
+ */
932
+ async addRootNode(treeComponent, newNode) {
933
+ const rootNode = treeComponent.findNode(ROOT_NODE_ID);
934
+ if (!rootNode) {
935
+ await this.refreshAfterChange();
936
+ return;
937
+ }
938
+ if (!treeComponent.isNodeExpanded(ROOT_NODE_ID)) {
939
+ await treeComponent.expandNode(ROOT_NODE_ID);
940
+ }
941
+ treeComponent.addChild(ROOT_NODE_ID, newNode);
942
+ }
943
+ /**
944
+ * Updates an existing node in the tree after edit operation
945
+ */
946
+ async updateNodeInTree(nodeId, entityData) {
947
+ if (!this.isTreeInitialized()) {
948
+ return;
949
+ }
950
+ const treeComponent = this.tree();
951
+ if (!treeComponent) {
952
+ return;
953
+ }
954
+ const existingNode = treeComponent.findNode(nodeId);
955
+ if (!existingNode) {
956
+ await this.refreshNodeParent(entityData);
957
+ return;
958
+ }
959
+ // Check if parent changed
960
+ const parentKey = this.treeData?.categoryEntityDef?.parentKey;
961
+ let newParentId = undefined;
962
+ if (parentKey && entityData[parentKey]) {
963
+ const parentValue = String(entityData[parentKey]);
964
+ // 'all' means root, so treat it as undefined
965
+ newParentId = parentValue !== 'all' && parentValue !== '' ? parentValue : undefined;
966
+ }
967
+ const currentParent = treeComponent.getParent(nodeId);
968
+ const currentParentId = currentParent ? String(currentParent['id'] ?? '') : undefined;
969
+ // Normalize current parent: 'all' means root (undefined)
970
+ const normalizedCurrentParentId = currentParentId === 'all' || currentParentId === '' ? undefined : currentParentId;
971
+ // Handle parent change: move node to new parent if parent changed
972
+ if (newParentId !== normalizedCurrentParentId) {
973
+ // If parent changed, move the node first
974
+ const moved = treeComponent.moveNode(nodeId, newParentId);
975
+ if (!moved) {
976
+ // If move failed, refresh the tree to ensure consistency
977
+ await this.refreshNodeParent(entityData);
978
+ return;
979
+ }
980
+ // After moving, we need to ensure the new parent is expanded
981
+ if (newParentId) {
982
+ if (!treeComponent.isNodeExpanded(newParentId)) {
983
+ await treeComponent.expandNode(newParentId);
984
+ }
985
+ }
986
+ else {
987
+ // Moving to root - ensure root is expanded
988
+ if (!treeComponent.isNodeExpanded(ROOT_NODE_ID)) {
989
+ await treeComponent.expandNode(ROOT_NODE_ID);
990
+ }
991
+ }
992
+ }
993
+ // Update node properties (title, data, etc.)
994
+ const updatedNode = this.categoryTreeService.convertToTreeNode(entityData, this.treeConfig);
995
+ const updates = {
996
+ ['title']: updatedNode['title'],
997
+ ['data']: updatedNode['data'],
998
+ ['childrenCount']: updatedNode['childrenCount'],
999
+ ['icon']: updatedNode['icon'],
1000
+ };
1001
+ treeComponent.editNode(nodeId, updates);
1002
+ }
1003
+ /**
1004
+ * Removes a node from the tree after delete operation
1005
+ */
1006
+ async removeNodeFromTree(nodeId) {
1007
+ const treeComponent = this.tree();
1008
+ if (!treeComponent) {
1009
+ return;
1010
+ }
1011
+ const nodeToDelete = treeComponent.findNode(nodeId);
1012
+ if (!nodeToDelete) {
1013
+ await this.refreshAfterChange();
1014
+ return;
1015
+ }
1016
+ treeComponent.removeNode(nodeId);
1017
+ }
1018
+ //#endregion
1019
+ //#region ---- Entity CRUD (Commands) ----
1020
+ /**
1021
+ * Executes Entity:Create command for category (standard command-based create).
1022
+ */
1023
+ async executeCreateCommand(parentId) {
1024
+ const entityKey = this.categoryEntityKey();
1025
+ const [moduleName, entityName] = entityKey.split('.');
1026
+ const entityRef = await this.entityRegistry.resolve(moduleName, entityName);
1027
+ const data = {};
1028
+ if (parentId && this.treeData?.categoryEntityDef?.parentKey) {
1029
+ data[this.treeData.categoryEntityDef.parentKey] = parentId;
1030
+ }
1031
+ const createCategoryTitle = await this.translate.translateAsync('@general:actions.create-category.title');
1032
+ const result = await this.commandService.execute('Entity:Create', {
1033
+ __context__: {
1034
+ entity: entityKey,
1035
+ entityInfo: {
1036
+ name: entityRef.name,
1037
+ module: entityRef.module,
1038
+ title: entityRef.title ?? entityRef.formats?.individual ?? '',
1039
+ parentKey: entityRef.parentKey,
1040
+ },
1041
+ data: Object.keys(data).length > 0 ? data : undefined,
1042
+ options: {
1043
+ process: { redirect: false, canCreateNewOne: !!parentId },
1044
+ decoration: { header: { title: createCategoryTitle } },
1045
+ },
1046
+ },
1047
+ });
1048
+ return result ?? { success: false };
1049
+ }
1050
+ /**
1051
+ * Executes Entity:Update command for category (standard command-based update).
1052
+ */
1053
+ async executeUpdateCommand(nodeData) {
1054
+ const entityKey = this.categoryEntityKey();
1055
+ const [moduleName, entityName] = entityKey.split('.');
1056
+ const entityRef = await this.entityRegistry.resolve(moduleName, entityName);
1057
+ const result = await this.commandService.execute('Entity:Update', {
1058
+ __context__: {
1059
+ entity: entityKey,
1060
+ entityInfo: {
1061
+ name: entityRef.name,
1062
+ module: entityRef.module,
1063
+ title: entityRef.title ?? entityRef.formats?.individual ?? '',
1064
+ parentKey: entityRef.parentKey,
1065
+ },
1066
+ data: nodeData,
1067
+ options: { layout: { size: 'md' } },
1068
+ },
1069
+ });
1070
+ return result ?? { success: false };
1071
+ }
1072
+ /**
1073
+ * Applies create command result to the tree (add node or refresh).
1074
+ */
1075
+ async applyCreateResult(result, parentId) {
1076
+ if (!result.success || !this.isTreeInitialized()) {
1077
+ await this.refreshAfterChange(parentId);
1078
+ return;
1079
+ }
1080
+ const newEntityData = result.data;
1081
+ if (newEntityData) {
1082
+ await this.addNodeToTree(newEntityData, parentId);
1083
+ }
1084
+ else {
1085
+ await this.refreshAfterChange(parentId);
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Applies update command result to the tree (update node or refresh).
1090
+ */
1091
+ async applyUpdateResult(result, node) {
1092
+ if (!result.success || !this.isTreeInitialized()) {
1093
+ await this.refreshNodeParent(this.extractNodeData(node));
1094
+ return;
1095
+ }
1096
+ const updatedEntityData = result.data;
1097
+ if (updatedEntityData) {
1098
+ await this.updateNodeInTree(this.extractNodeId(node), updatedEntityData);
1099
+ }
1100
+ else {
1101
+ await this.refreshNodeParent(this.extractNodeData(node));
1102
+ }
1103
+ }
1104
+ //#endregion
1105
+ //#region ---- Delete (Workflow) ----
1106
+ /**
1107
+ * Executes delete-entity workflow (platform has no Entity:Delete command).
1108
+ */
1109
+ async executeDeleteWorkflow(nodeData) {
1110
+ return await this.workflow.execute('delete-entity', {
1111
+ entity: this.categoryEntityKey(),
1112
+ data: nodeData,
1113
+ options: { process: { showResult: true } },
1114
+ });
1115
+ }
1116
+ /**
1117
+ * Handles the result of a delete operation
1118
+ */
1119
+ async handleDeleteResult(context, node) {
1120
+ const result = context.getOutput('result');
1121
+ if (result) {
1122
+ await this.removeNodeFromTree(this.extractNodeId(node));
1123
+ }
1124
+ else {
1125
+ await this.refreshNodeParent(this.extractNodeData(node));
1126
+ }
1127
+ }
1128
+ //#endregion
1129
+ //#region ---- Utility Methods ----
1130
+ /**
1131
+ * Checks if tree data and config are initialized
1132
+ */
1133
+ isTreeInitialized() {
1134
+ return !!(this.treeData && this.treeConfig);
1135
+ }
1136
+ /**
1137
+ * Extracts node data from a tree node
1138
+ */
1139
+ extractNodeData(node) {
1140
+ return (node['data'] || node);
1141
+ }
1142
+ /**
1143
+ * Extracts node ID from a tree node
1144
+ */
1145
+ extractNodeId(node) {
1146
+ return String(node['id'] ?? '');
1147
+ }
1148
+ /**
1149
+ * Builds category filters from node data and apply conditions
1150
+ */
1151
+ buildCategoryFilters(nodeData, applyConditions) {
1152
+ return applyConditions
1153
+ .map((condition) => {
1154
+ const value = nodeData[condition.value];
1155
+ if (value === 'all' || value == null) {
1156
+ return null;
1157
+ }
1158
+ return {
1159
+ field: condition.name,
1160
+ value,
1161
+ operator: condition.operator,
1162
+ };
1163
+ })
1164
+ .filter((item) => item !== null);
1165
+ }
1166
+ /**
1167
+ * Builds view filters from view conditions
1168
+ */
1169
+ buildViewFilters() {
1170
+ return this.vm()
1171
+ .view()
1172
+ .conditions.map((condition) => ({ ...condition, field: condition.name }));
1173
+ }
1174
+ /**
1175
+ * Refreshes the parent node of the given entity data
1176
+ */
1177
+ async refreshNodeParent(entityData) {
1178
+ const parentKey = this.treeData?.categoryEntityDef?.parentKey;
1179
+ const parentId = parentKey && entityData[parentKey] ? entityData[parentKey] : undefined;
1180
+ await this.refreshAfterChange(parentId);
1181
+ }
1182
+ /**
1183
+ * Prevents default behavior and stops event propagation
1184
+ */
1185
+ preventDefaultAndStopPropagation(event) {
1186
+ event.nativeEvent.preventDefault();
1187
+ event.nativeEvent.stopPropagation();
1188
+ }
1189
+ //#endregion
1190
+ //#region ---- Loading State Management ----
1191
+ /**
1192
+ * Sets loading state with delay to avoid flickering for fast responses
1193
+ */
1194
+ setLoadingWithDelay(loading) {
1195
+ if (!loading) {
1196
+ return;
1197
+ }
1198
+ if (this.loadingTimeoutId) {
1199
+ clearTimeout(this.loadingTimeoutId);
1200
+ }
1201
+ this.loadingTimeoutId = setTimeout(() => {
1202
+ this.isLoading.set(true);
1203
+ this.loadingTimeoutId = null;
1204
+ }, LOADING_DELAY_MS);
1205
+ }
1206
+ /**
1207
+ * Clears loading state and cancels any pending timeout
1208
+ */
1209
+ clearLoadingState() {
1210
+ if (this.loadingTimeoutId) {
1211
+ clearTimeout(this.loadingTimeoutId);
1212
+ this.loadingTimeoutId = null;
1213
+ }
1214
+ this.isLoading.set(false);
1215
+ }
1216
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityCategoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1217
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityCategoryComponent, isStandalone: true, selector: "axp-entity-category", inputs: { vm: { classPropertyName: "vm", publicName: "vm", isSignal: true, isRequired: true, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null }, selectMode: { classPropertyName: "selectMode", publicName: "selectMode", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, dragArea: { classPropertyName: "dragArea", publicName: "dragArea", isSignal: true, isRequired: false, transformFunction: null }, dragBehavior: { classPropertyName: "dragBehavior", publicName: "dragBehavior", isSignal: true, isRequired: false, transformFunction: null }, showIcons: { classPropertyName: "showIcons", publicName: "showIcons", isSignal: true, isRequired: false, transformFunction: null }, showChildrenBadge: { classPropertyName: "showChildrenBadge", publicName: "showChildrenBadge", isSignal: true, isRequired: false, transformFunction: null }, expandedIcon: { classPropertyName: "expandedIcon", publicName: "expandedIcon", isSignal: true, isRequired: false, transformFunction: null }, collapsedIcon: { classPropertyName: "collapsedIcon", publicName: "collapsedIcon", isSignal: true, isRequired: false, transformFunction: null }, indentSize: { classPropertyName: "indentSize", publicName: "indentSize", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, searchWithChildren: { classPropertyName: "searchWithChildren", publicName: "searchWithChildren", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true, isSignal: true }], ngImport: i0, template: "<axp-layout-header>\n <axp-layout-title>{{\n vm().entityDef.category?.title || '@general:terms.classification.category' | translate | async\n }}</axp-layout-title>\n <axp-layout-toolbar>\n <div class=\"ax-flex ax-flex-col ax-gap-1 ax-w-full\">\n <ax-search-box\n (onValueChanged)=\"handleCategorySearchChange($event)\"\n [delayTime]=\"300\"\n [placeholder]=\"'@general:terms.interface.category.search.placeholder' | translate | async\"\n >\n </ax-search-box>\n @if (isSearching() && currentSearchValue().trim()) {\n <div class=\"ax-text-xs ax-text-muted ax-flex ax-items-center ax-gap-1\">\n <span>{{ '@general:terms.interface.category.search.searching' | translate | async }}</span>\n </div>\n } @else if (currentSearchValue().trim() && !isSearching()) {\n <div class=\"ax-text-xs ax-text-muted ax-flex ax-items-center ax-gap-1\">\n @if (searchResultCount() > 0) {\n <span>{{ resultsFoundText() }}</span>\n }\n </div>\n }\n </div>\n </axp-layout-toolbar>\n</axp-layout-header>\n<axp-layout-content>\n @if (isLoading()) {\n <div class=\"ax-p-4 ax-flex ax-flex-col ax-gap-3\">\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n </div>\n } @else if (showNoSearchResults()) {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-search\"\n [title]=\"'@general:terms.interface.category.search.no-results-found.title'\"\n [description]=\"'@general:terms.interface.category.search.no-results-found.description'\"\n >\n </axp-state-message>\n </div>\n } @else if (treeData) {\n <div class=\"ax-px-4 ax-max-h-[calc(100vh-250px)] ax-overflow-auto\">\n <ax-tree-view\n [datasource]=\"datasource\"\n [selectMode]=\"selectMode()\"\n [selectionBehavior]=\"selectionBehavior()\"\n [dragArea]=\"dragArea()\"\n [dragBehavior]=\"dragBehavior()\"\n [showIcons]=\"showIcons()\"\n [showChildrenBadge]=\"showChildrenBadge()\"\n [expandedIcon]=\"expandedIcon()\"\n [collapsedIcon]=\"collapsedIcon()\"\n [indentSize]=\"indentSize()\"\n [look]=\"look()\"\n [titleField]=\"textField()\"\n [idField]=\"valueField()\"\n [expandOnDoubleClick]=\"true\"\n [nodeTemplate]=\"itemTemplate\"\n (onNodeToggle)=\"onNodeToggle($event)\"\n (onNodeClick)=\"handleNodeClick($event.node)\"\n #tree\n >\n </ax-tree-view>\n </div>\n } @else {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:terms.interface.category.search.no-records.title'\"\n [description]=\"'@general:terms.interface.category.search.no-records.description'\"\n >\n @if (!categoryReadonly()) {\n <ax-button\n slot=\"actions\"\n (onClick)=\"handleCreateRootClick($event)\"\n look=\"solid\"\n color=\"primary\"\n [text]=\"'@general:actions.add-new.title' | translate | async\"\n >\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button>\n }\n </axp-state-message>\n </div>\n }\n\n <ng-template #itemTemplate let-node=\"node\" let-level=\"level\">\n @let item = node.data || node;\n @let textField = vm().entityDef.category?.textField || 'title';\n @let valueField = vm().entityDef.category?.valueField || 'id';\n @let itemId = item[valueField] || node.id;\n @let rawLabel = item[textField] || node.title;\n @let itemTitle = rawLabel | translate | async;\n <div class=\"ax-flex ax-items-center ax-justify-between ax-w-full ax-gap-2 ax-overflow-hidden ax-py-1\">\n <div class=\"ax-flex ax-items-center ax-gap-2 ax-min-w-0\">\n <ax-icon\n class=\"fas fa-folder\"\n [style.color]=\"item.color ?? 'rgba(var(--ax-sys-color-warning-500), 1)'\"\n ></ax-icon>\n <span class=\"ax-truncate\">\n @if (itemId === 'all') {\n {{ categoryTreeRootTitleI18nKey | translate | async }}\n } @else {\n {{ itemTitle }}\n }\n </span>\n </div>\n @if (itemId && itemId !== 'all' && !categoryReadonly()) {\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-button class=\"ax-xs\" color=\"default\" look=\"blank\" (onClick)=\"$event.nativeEvent.stopPropagation()\">\n <ax-icon class=\"fas fa-ellipsis-v\"></ax-icon>\n <ax-dropdown-panel>\n <ax-button-item-list>\n <ax-button-item\n (onClick)=\"handleCreateChildClick(node, $event)\"\n look=\"blank\"\n color=\"default\"\n [text]=\"('@general:actions.add-new-child.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button-item>\n <ax-button-item\n (onClick)=\"handleEditNodeClick(node, $event)\"\n look=\"blank\"\n [text]=\"('@general:actions.edit.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-pen\"></ax-icon>\n </ax-button-item>\n <ax-button-item\n (onClick)=\"handleDeleteNodeClick(node, $event)\"\n color=\"danger\"\n look=\"blank\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-trash\"></ax-icon>\n </ax-button-item>\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n </div>\n } @else if (itemId === 'all' && !categoryReadonly()) {\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-button class=\"ax-xs\" (onClick)=\"handleCreateRootClick($event)\" look=\"blank\" color=\"default\">\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button>\n </div>\n }\n </div>\n </ng-template>\n</axp-layout-content>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTreeViewModule }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay", "controlledSelection", "selectedIds"], outputs: ["datasourceChange", "selectedIdsChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i2$2.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i6.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "component", type: AXPThemeLayoutHeaderComponent, selector: "axp-layout-header" }, { kind: "component", type: AXPThemeLayoutToolbarComponent, selector: "axp-layout-toolbar" }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i5.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None }); }
1218
+ }
1219
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityCategoryComponent, decorators: [{
1220
+ type: Component,
1221
+ args: [{ selector: 'axp-entity-category', standalone: true, encapsulation: ViewEncapsulation.None, imports: [
1222
+ CommonModule,
1223
+ AXDecoratorModule,
1224
+ AXTreeViewModule,
1225
+ AXTreeViewComponent,
1226
+ AXSkeletonModule,
1227
+ AXPThemeLayoutBlockComponent,
1228
+ AXSearchBoxModule,
1229
+ AXPThemeLayoutHeaderComponent,
1230
+ AXPThemeLayoutToolbarComponent,
1231
+ AXTranslationModule,
1232
+ AXButtonModule,
1233
+ AXDropdownModule,
1234
+ AXPStateMessageComponent,
1235
+ ], template: "<axp-layout-header>\n <axp-layout-title>{{\n vm().entityDef.category?.title || '@general:terms.classification.category' | translate | async\n }}</axp-layout-title>\n <axp-layout-toolbar>\n <div class=\"ax-flex ax-flex-col ax-gap-1 ax-w-full\">\n <ax-search-box\n (onValueChanged)=\"handleCategorySearchChange($event)\"\n [delayTime]=\"300\"\n [placeholder]=\"'@general:terms.interface.category.search.placeholder' | translate | async\"\n >\n </ax-search-box>\n @if (isSearching() && currentSearchValue().trim()) {\n <div class=\"ax-text-xs ax-text-muted ax-flex ax-items-center ax-gap-1\">\n <span>{{ '@general:terms.interface.category.search.searching' | translate | async }}</span>\n </div>\n } @else if (currentSearchValue().trim() && !isSearching()) {\n <div class=\"ax-text-xs ax-text-muted ax-flex ax-items-center ax-gap-1\">\n @if (searchResultCount() > 0) {\n <span>{{ resultsFoundText() }}</span>\n }\n </div>\n }\n </div>\n </axp-layout-toolbar>\n</axp-layout-header>\n<axp-layout-content>\n @if (isLoading()) {\n <div class=\"ax-p-4 ax-flex ax-flex-col ax-gap-3\">\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n <ax-skeleton class=\"ax-w-full ax-h-6 ax-rounded-md\"></ax-skeleton>\n </div>\n } @else if (showNoSearchResults()) {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-search\"\n [title]=\"'@general:terms.interface.category.search.no-results-found.title'\"\n [description]=\"'@general:terms.interface.category.search.no-results-found.description'\"\n >\n </axp-state-message>\n </div>\n } @else if (treeData) {\n <div class=\"ax-px-4 ax-max-h-[calc(100vh-250px)] ax-overflow-auto\">\n <ax-tree-view\n [datasource]=\"datasource\"\n [selectMode]=\"selectMode()\"\n [selectionBehavior]=\"selectionBehavior()\"\n [dragArea]=\"dragArea()\"\n [dragBehavior]=\"dragBehavior()\"\n [showIcons]=\"showIcons()\"\n [showChildrenBadge]=\"showChildrenBadge()\"\n [expandedIcon]=\"expandedIcon()\"\n [collapsedIcon]=\"collapsedIcon()\"\n [indentSize]=\"indentSize()\"\n [look]=\"look()\"\n [titleField]=\"textField()\"\n [idField]=\"valueField()\"\n [expandOnDoubleClick]=\"true\"\n [nodeTemplate]=\"itemTemplate\"\n (onNodeToggle)=\"onNodeToggle($event)\"\n (onNodeClick)=\"handleNodeClick($event.node)\"\n #tree\n >\n </ax-tree-view>\n </div>\n } @else {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:terms.interface.category.search.no-records.title'\"\n [description]=\"'@general:terms.interface.category.search.no-records.description'\"\n >\n @if (!categoryReadonly()) {\n <ax-button\n slot=\"actions\"\n (onClick)=\"handleCreateRootClick($event)\"\n look=\"solid\"\n color=\"primary\"\n [text]=\"'@general:actions.add-new.title' | translate | async\"\n >\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button>\n }\n </axp-state-message>\n </div>\n }\n\n <ng-template #itemTemplate let-node=\"node\" let-level=\"level\">\n @let item = node.data || node;\n @let textField = vm().entityDef.category?.textField || 'title';\n @let valueField = vm().entityDef.category?.valueField || 'id';\n @let itemId = item[valueField] || node.id;\n @let rawLabel = item[textField] || node.title;\n @let itemTitle = rawLabel | translate | async;\n <div class=\"ax-flex ax-items-center ax-justify-between ax-w-full ax-gap-2 ax-overflow-hidden ax-py-1\">\n <div class=\"ax-flex ax-items-center ax-gap-2 ax-min-w-0\">\n <ax-icon\n class=\"fas fa-folder\"\n [style.color]=\"item.color ?? 'rgba(var(--ax-sys-color-warning-500), 1)'\"\n ></ax-icon>\n <span class=\"ax-truncate\">\n @if (itemId === 'all') {\n {{ categoryTreeRootTitleI18nKey | translate | async }}\n } @else {\n {{ itemTitle }}\n }\n </span>\n </div>\n @if (itemId && itemId !== 'all' && !categoryReadonly()) {\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-button class=\"ax-xs\" color=\"default\" look=\"blank\" (onClick)=\"$event.nativeEvent.stopPropagation()\">\n <ax-icon class=\"fas fa-ellipsis-v\"></ax-icon>\n <ax-dropdown-panel>\n <ax-button-item-list>\n <ax-button-item\n (onClick)=\"handleCreateChildClick(node, $event)\"\n look=\"blank\"\n color=\"default\"\n [text]=\"('@general:actions.add-new-child.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button-item>\n <ax-button-item\n (onClick)=\"handleEditNodeClick(node, $event)\"\n look=\"blank\"\n [text]=\"('@general:actions.edit.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-pen\"></ax-icon>\n </ax-button-item>\n <ax-button-item\n (onClick)=\"handleDeleteNodeClick(node, $event)\"\n color=\"danger\"\n look=\"blank\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n >\n <ax-icon class=\"fas fa-trash\"></ax-icon>\n </ax-button-item>\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n </div>\n } @else if (itemId === 'all' && !categoryReadonly()) {\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-button class=\"ax-xs\" (onClick)=\"handleCreateRootClick($event)\" look=\"blank\" color=\"default\">\n <ax-icon class=\"fas fa-plus\"></ax-icon>\n </ax-button>\n </div>\n }\n </div>\n </ng-template>\n</axp-layout-content>\n" }]
1236
+ }], ctorParameters: () => [], propDecorators: { vm: [{ type: i0.Input, args: [{ isSignal: true, alias: "vm", required: true }] }], tree: [{ type: i0.ViewChild, args: ['tree', { isSignal: true }] }], searchValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchValue", required: false }] }], selectMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectMode", required: false }] }], selectionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionBehavior", required: false }] }], dragArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragArea", required: false }] }], dragBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragBehavior", required: false }] }], showIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcons", required: false }] }], showChildrenBadge: [{ type: i0.Input, args: [{ isSignal: true, alias: "showChildrenBadge", required: false }] }], expandedIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIcon", required: false }] }], collapsedIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedIcon", required: false }] }], indentSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentSize", required: false }] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], searchWithChildren: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchWithChildren", required: false }] }] } });
1237
+
1238
+ class AXPEntityMasterToolbarViewComponent {
1239
+ constructor() {
1240
+ this.layoutService = inject(AXPLayoutThemeService);
1241
+ this.deviceService = inject(AXPDeviceService);
1242
+ this.parent = inject(AXPEntityMasterListViewComponent);
1243
+ this.router = inject(Router);
1244
+ this.activeRoute = inject(ActivatedRoute);
1245
+ this.settingsService = inject(AXPSettingsService);
1246
+ this.injector = inject(Injector);
1247
+ this.isInitializing = false;
1248
+ this.pendingInitialSorts = true;
1249
+ this.filterTriggerMode = 'manual';
1250
+ this.previousFilterQueries = signal([], ...(ngDevMode ? [{ debugName: "previousFilterQueries" }] : /* istanbul ignore next */ []));
1251
+ this.initialFilters = signal([], ...(ngDevMode ? [{ debugName: "initialFilters" }] : /* istanbul ignore next */ []));
1252
+ this.filtersDefinitions = signal([], ...(ngDevMode ? [{ debugName: "filtersDefinitions" }] : /* istanbul ignore next */ []));
1253
+ this.viewQueries = computed(() => this.vm.views().map((v) => ({
1254
+ name: v.name,
1255
+ fixed: false,
1256
+ title: v.title,
1257
+ columns: v.columns,
1258
+ conditions: v.conditions.map((c) => ({
1259
+ field: c.name,
1260
+ operator: c.operator,
1261
+ value: c.value,
1262
+ })),
1263
+ sorts: v.sorts,
1264
+ })), ...(ngDevMode ? [{ debugName: "viewQueries" }] : /* istanbul ignore next */ []));
1265
+ this.selectedViewQuery = computed(() => ({
1266
+ name: this.vm.view().name,
1267
+ fixed: false,
1268
+ title: this.vm.view().title,
1269
+ columns: this.vm.view().columns,
1270
+ conditions: this.vm.view().conditions.map((c) => ({
1271
+ field: c.name,
1272
+ operator: c.operator,
1273
+ value: c.value,
1274
+ })),
1275
+ sorts: this.vm.view().sorts,
1276
+ }), ...(ngDevMode ? [{ debugName: "selectedViewQuery" }] : /* istanbul ignore next */ []));
1277
+ this.isFiltersDirty = computed(() => !isEqual(this.vm.filterQueries(), this.previousFilterQueries()), ...(ngDevMode ? [{ debugName: "isFiltersDirty" }] : /* istanbul ignore next */ []));
1278
+ this.showListDisplayModeControl = computed(() => this.vm.enabledListLayouts().includes('card'), ...(ngDevMode ? [{ debugName: "showListDisplayModeControl" }] : /* istanbul ignore next */ []));
1279
+ this.listDisplayMode = computed(() => this.vm.listDisplayMode(), ...(ngDevMode ? [{ debugName: "listDisplayMode" }] : /* istanbul ignore next */ []));
1280
+ this.hasTableListDisplayOption = computed(() => this.vm.enabledListLayouts().includes('table'), ...(ngDevMode ? [{ debugName: "hasTableListDisplayOption" }] : /* istanbul ignore next */ []));
1281
+ this.listDisplayModeButtonIcon = computed(() => {
1282
+ switch (this.listDisplayMode()) {
1283
+ case 'table':
1284
+ return 'fa-light fa-table';
1285
+ case 'card-compact':
1286
+ return 'fa-light fa-grip-lines';
1287
+ case 'card-expanded':
1288
+ return 'fa-light fa-grid-2';
1289
+ }
1290
+ }, ...(ngDevMode ? [{ debugName: "listDisplayModeButtonIcon" }] : /* istanbul ignore next */ []));
1291
+ this.listDisplayModePopover = viewChild('listDisplayModePopover', ...(ngDevMode ? [{ debugName: "listDisplayModePopover" }] : /* istanbul ignore next */ []));
1292
+ // Debounced apply to coalesce multiple UI changes (filters/sorts/columns)
1293
+ this.applyTimer = null;
1294
+ this.#effect = effect(() => {
1295
+ // Keep definitions up to date; initial filters are set explicitly from settings or view
1296
+ this.filtersDefinitions.set(this.vm.filterDefinitions());
1297
+ }, ...(ngDevMode ? [{ debugName: "#effect" }] : /* istanbul ignore next */ []));
1298
+ this.sortQueries = computed(() => this.vm
1299
+ .sortedFields()
1300
+ .filter((s) => !!s.dir)
1301
+ .map((s) => ({
1302
+ name: s.name,
1303
+ dir: s.dir,
1304
+ })), ...(ngDevMode ? [{ debugName: "sortQueries" }] : /* istanbul ignore next */ []));
1305
+ this.sortDefinitions = computed(() => {
1306
+ const sortable = this.vm.sortableFields();
1307
+ const active = this.vm.sortedFields();
1308
+ const activeNames = new Set(active.map((s) => s.name));
1309
+ const activeDefs = active.map((s) => ({
1310
+ name: s.name,
1311
+ title: s.title,
1312
+ dir: s.dir,
1313
+ }));
1314
+ const inactiveDefs = sortable
1315
+ .filter((s) => !activeNames.has(s.name))
1316
+ .map((s) => ({
1317
+ name: s.name,
1318
+ title: s.title,
1319
+ dir: undefined,
1320
+ }));
1321
+ return [...activeDefs, ...inactiveDefs];
1322
+ }, ...(ngDevMode ? [{ debugName: "sortDefinitions" }] : /* istanbul ignore next */ []));
1323
+ }
1324
+ async ngOnInit() {
1325
+ // Prefer saved settings (view/columns/sorts/filters) if available, then apply once
1326
+ await this.loadSettings();
1327
+ const viewFromUrl = this.activeRoute.snapshot.queryParamMap.get('view');
1328
+ // TODO: Consolidate list init setView calls (resolver, list-view queryParamSub / ngAfterViewInit).
1329
+ await this.vm.setView(viewFromUrl);
1330
+ await this.vm.ensureListPagingResolved();
1331
+ this.filtersDefinitions.set(this.vm.filterDefinitions());
1332
+ await this.runInitialListLoad();
1333
+ }
1334
+ /**
1335
+ * Applies persisted view/filters/sorts once after query-filters syncs `initialFilters`.
1336
+ * Avoids a second list query from `scheduleApply` plus a follow-up `onFiltersChanged` emit.
1337
+ */
1338
+ async runInitialListLoad() {
1339
+ traceEntityMasterList('toolbar', 'runInitialListLoad start', () => ({
1340
+ viewName: this.vm.view().name,
1341
+ activeListLayout: this.vm.activeListLayout(),
1342
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
1343
+ }));
1344
+ this.isInitializing = true;
1345
+ this.pendingInitialSorts = true;
1346
+ this.setInitialFiltersFromSettingsOrView();
1347
+ await this.waitForQueryFiltersSync();
1348
+ await this.vm.applyFilterAndSort();
1349
+ this.previousFilterQueries.set(this.vm.filterQueries());
1350
+ this.isInitializing = false;
1351
+ this.pendingInitialSorts = false;
1352
+ traceEntityMasterList('toolbar', 'runInitialListLoad complete', () => ({
1353
+ viewName: this.vm.view().name,
1354
+ activeListLayout: this.vm.activeListLayout(),
1355
+ filterCount: this.vm.filterQueries().length,
1356
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
1357
+ }));
1358
+ }
1359
+ /** Waits until query-filters has rendered and synced converted `initialFilters`. */
1360
+ waitForQueryFiltersSync() {
1361
+ return new Promise((resolve) => {
1362
+ afterNextRender(() => {
1363
+ queueMicrotask(() => resolve());
1364
+ }, { injector: this.injector });
1365
+ });
1366
+ }
1367
+ async loadSettings() {
1368
+ const filterTriggerMode = await this.settingsService.get(AXPCommonSettings.EntityFilterApplyMode);
1369
+ this.filterTriggerMode = (filterTriggerMode?.value ?? 'auto');
1370
+ }
1371
+ setListDisplayMode(mode) {
1372
+ traceEntityMasterList('toolbar', 'setListDisplayMode clicked', () => ({
1373
+ mode,
1374
+ viewName: this.vm.view().name,
1375
+ previousLayout: this.vm.activeListLayout(),
1376
+ previousDisplayMode: this.vm.listDisplayMode(),
1377
+ }));
1378
+ this.vm.setListDisplayMode(mode);
1379
+ this.listDisplayModePopover()?.close();
1380
+ }
1381
+ scheduleApply() {
1382
+ clearTimeout(this.applyTimer);
1383
+ this.applyTimer = setTimeout(() => {
1384
+ this.vm.applyFilterAndSort();
1385
+ }, 50);
1386
+ }
1387
+ async onViewChanged(view) {
1388
+ traceEntityMasterList('toolbar', 'view tab changed', () => ({
1389
+ previousViewName: this.vm.view().name,
1390
+ nextViewName: view.name,
1391
+ activeListLayout: this.vm.activeListLayout(),
1392
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
1393
+ }));
1394
+ await this.vm.setView(view.name);
1395
+ if (axpIsEntityListRoute(this.router)) {
1396
+ axpReplaceEntityListRouteQueryParams(this.router, { view: view.name });
1397
+ }
1398
+ await this.runInitialListLoad();
1399
+ traceEntityMasterList('toolbar', 'view tab change handled', () => ({
1400
+ viewName: this.vm.view().name,
1401
+ activeListLayout: this.vm.activeListLayout(),
1402
+ filterCount: this.vm.filterQueries().length,
1403
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
1404
+ }));
1405
+ }
1406
+ #effect;
1407
+ setInitialFiltersFromSettingsOrView() {
1408
+ const saved = this.vm.filterQueries();
1409
+ this.previousFilterQueries.set(saved);
1410
+ const viewConditions = this.vm.view().conditions.map((c) => c.name);
1411
+ if (saved && saved.length) {
1412
+ this.initialFilters.set(saved.map((s) => ({ ...s, hidden: viewConditions.includes(s.field) })));
1413
+ return;
1414
+ }
1415
+ this.initialFilters.set(this.vm.view().conditions.map((c) => ({
1416
+ field: c.name,
1417
+ operator: c.operator,
1418
+ value: c.value,
1419
+ hidden: true,
1420
+ })));
1421
+ }
1422
+ onFiltersChanged(filters) {
1423
+ if (this.isInitializing) {
1424
+ this.vm.filterQueries.set(filters);
1425
+ this.previousFilterQueries.set(filters);
1426
+ return;
1427
+ }
1428
+ this.vm.filterQueries.set(filters);
1429
+ if (this.filterTriggerMode === 'auto') {
1430
+ this.applyFilters();
1431
+ }
1432
+ }
1433
+ applyFilters() {
1434
+ this.previousFilterQueries.set(this.vm.filterQueries());
1435
+ this.vm.saveSettings('filters', this.vm.filterQueries());
1436
+ void this.vm.applyFilterAndSort();
1437
+ }
1438
+ onSortQueriesChange(e) {
1439
+ if (this.isInitializing || this.pendingInitialSorts) {
1440
+ this.pendingInitialSorts = false;
1441
+ return;
1442
+ }
1443
+ const sortableByName = new Map(this.vm.sortableFields().map((f) => [f.name, f]));
1444
+ const nextSortedFields = e
1445
+ .filter((s) => s.dir && sortableByName.has(s.name))
1446
+ .map((s) => ({
1447
+ name: s.name,
1448
+ title: sortableByName.get(s.name).title,
1449
+ dir: s.dir,
1450
+ }));
1451
+ const sortSignature = (fields) => fields.map((s) => `${s.name}:${s.dir ?? ''}`).join('|');
1452
+ if (sortSignature(nextSortedFields) === sortSignature(this.vm.sortedFields())) {
1453
+ return;
1454
+ }
1455
+ this.vm.sortedFields.set(nextSortedFields);
1456
+ this.vm.saveSettings('sorts', e);
1457
+ this.scheduleApply();
1458
+ }
1459
+ onColumnsChange(columns) {
1460
+ const keyOf = (c) => c.column?.options?.dataPath ?? c.name;
1461
+ this.vm.columns.update((prev) => {
1462
+ const byKey = new Map(prev.map((c) => [keyOf(c), c]));
1463
+ return columns
1464
+ .map((newColumn) => {
1465
+ const existing = byKey.get(keyOf(newColumn));
1466
+ if (!existing) {
1467
+ return null;
1468
+ }
1469
+ return new AXPEntityListViewColumnViewModel(existing.property, {
1470
+ ...existing.column,
1471
+ options: {
1472
+ ...existing.column.options,
1473
+ visible: newColumn.visible,
1474
+ },
1475
+ });
1476
+ })
1477
+ .filter((c) => c != null);
1478
+ });
1479
+ // Persist column order/visibility
1480
+ this.vm.onColumnsChanged(this.vm.columns());
1481
+ // Use debounced apply to avoid triple refresh when combined with sorts/filters
1482
+ // this.scheduleApply();
1483
+ }
1484
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterToolbarViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1485
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterToolbarViewComponent, isStandalone: true, selector: "axp-entity-master-toolbar-view", inputs: { vm: ["viewModel", "vm"] }, host: { classAttribute: "ax-w-full" }, viewQueries: [{ propertyName: "listDisplayModePopover", first: true, predicate: ["listDisplayModePopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- <axp-entity-view-toolbar [viewModel]=\"vm\"></axp-entity-view-toolbar> -->\n<axp-query-views\n id=\"axp-toolbar-view-selector\"\n [views]=\"viewQueries()\"\n [selectedView]=\"selectedViewQuery()\"\n (selectedViewChange)=\"onViewChanged($event)\"\n></axp-query-views>\n<div class=\"ax-flex ax-items-center ax-gap-2 ax-border-b ax-border-light w-full\">\n <!-- <axp-entity-filter-toolbar [viewModel]=\"vm\"></axp-entity-filter-toolbar> -->\n <axp-query-filters\n id=\"axp-toolbar-filters\"\n [filtersDefinitions]=\"filtersDefinitions()\"\n [initialFilters]=\"initialFilters()\"\n (onFiltersChanged)=\"onFiltersChanged($event)\"\n ></axp-query-filters>\n\n <div class=\"ax-flex ax-items-center ax-gap-2 md:ax-gap-2\">\n @if (filterTriggerMode === 'manual' && isFiltersDirty()) {\n <ax-button\n id=\"axp-toolbar-btn-filter\"\n [title]=\"'@general:actions.apply.title'\"\n [iconOnly]=\"true\"\n #filterButton\n [color]=\"'primary'\"\n (onClick)=\"applyFilters()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-search\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (vm.activeListLayout() === 'table') {\n <div>\n <ax-button id=\"axp-toolbar-btn-columns\" [iconOnly]=\"true\" #columnButton [color]=\"'default'\">\n <i class=\"fa-light fa-table-columns\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-columns\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"columnButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #columnsPopover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.common.columns' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-4\">\n <axp-query-columns [columns]=\"vm.columns()\" (columnsChange)=\"onColumnsChange($event)\"></axp-query-columns>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n\n @if (vm.canSort()) {\n <div>\n <ax-button id=\"axp-toolbar-btn-sort\" [iconOnly]=\"true\" [text]=\"'Sorts'\" #sortButton [color]=\"'default'\">\n <i class=\"fa-light fa-sort-amount-up\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-sort\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"sortButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #popover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.common.sorts' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-4\">\n <!-- <axp-list-view-option-sorting [viewModel]=\"vm\"></axp-list-view-option-sorting> -->\n <axp-query-sorts\n [sortDefinitions]=\"sortDefinitions()\"\n (sortQueriesChange)=\"onSortQueriesChange($event)\"\n [initialSortQueries]=\"sortQueries()\"\n ></axp-query-sorts>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n @if (deviceService.isSmall()) {\n <ax-button\n id=\"axp-toolbar-btn-category\"\n (onClick)=\"parent.toggleCategoryDrawer()\"\n [iconOnly]=\"true\"\n [color]=\"'default'\"\n >\n <i class=\"fa-light fa-bars\"></i>\n </ax-button>\n }\n\n @if (showListDisplayModeControl()) {\n <div>\n <ax-button id=\"axp-toolbar-btn-list-display\" [iconOnly]=\"true\" #listDisplayButton [color]=\"'default'\">\n <i [class]=\"listDisplayModeButtonIcon()\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-list-display\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"listDisplayButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #listDisplayModePopover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.interface.list-display' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-2\">\n <ax-button-item-list>\n @if (hasTableListDisplayOption()) {\n <ax-button-item\n [text]=\"('@general:terms.interface.table-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'table'\"\n (onClick)=\"setListDisplayMode('table')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-table\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n @if (hasTableListDisplayOption()) {\n <ax-divider />\n }\n <ax-button-item\n [text]=\"('@general:terms.interface.card-compact-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'card-compact'\"\n (onClick)=\"setListDisplayMode('card-compact')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-grip-lines\"></i>\n </ax-prefix>\n </ax-button-item>\n <ax-button-item\n [text]=\"('@general:terms.interface.card-expanded-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'card-expanded'\"\n (onClick)=\"setListDisplayMode('card-expanded')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-grid-2\"></i>\n </ax-prefix>\n </ax-button-item>\n </ax-button-item-list>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPQueryFiltersComponent, selector: "axp-query-filters", inputs: ["filtersDefinitions", "initialFilters"], outputs: ["onFiltersChanged"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i3$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPQuerySortsComponent, selector: "axp-query-sorts", inputs: ["sortDefinitions", "initialSortQueries"], outputs: ["sortDefinitionsChange", "sortQueriesChange"] }, { kind: "component", type: AXPQueryViewsComponent, selector: "axp-query-views", inputs: ["views", "selectedView"], outputs: ["viewsChange", "selectedViewChange"] }, { kind: "component", type: AXPQueryColumnsComponent, selector: "axp-query-columns", inputs: ["columns"], outputs: ["columnsChange"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1486
+ }
1487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterToolbarViewComponent, decorators: [{
1488
+ type: Component,
1489
+ args: [{ selector: 'axp-entity-master-toolbar-view', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
1490
+ AsyncPipe,
1491
+ AXButtonModule,
1492
+ AXDecoratorModule,
1493
+ AXDropdownModule,
1494
+ AXTranslationModule,
1495
+ AXPQueryFiltersComponent,
1496
+ AXPopoverModule,
1497
+ AXPQuerySortsComponent,
1498
+ AXPQueryViewsComponent,
1499
+ AXPQueryColumnsComponent,
1500
+ ], host: {
1501
+ class: 'ax-w-full',
1502
+ }, template: "<!-- <axp-entity-view-toolbar [viewModel]=\"vm\"></axp-entity-view-toolbar> -->\n<axp-query-views\n id=\"axp-toolbar-view-selector\"\n [views]=\"viewQueries()\"\n [selectedView]=\"selectedViewQuery()\"\n (selectedViewChange)=\"onViewChanged($event)\"\n></axp-query-views>\n<div class=\"ax-flex ax-items-center ax-gap-2 ax-border-b ax-border-light w-full\">\n <!-- <axp-entity-filter-toolbar [viewModel]=\"vm\"></axp-entity-filter-toolbar> -->\n <axp-query-filters\n id=\"axp-toolbar-filters\"\n [filtersDefinitions]=\"filtersDefinitions()\"\n [initialFilters]=\"initialFilters()\"\n (onFiltersChanged)=\"onFiltersChanged($event)\"\n ></axp-query-filters>\n\n <div class=\"ax-flex ax-items-center ax-gap-2 md:ax-gap-2\">\n @if (filterTriggerMode === 'manual' && isFiltersDirty()) {\n <ax-button\n id=\"axp-toolbar-btn-filter\"\n [title]=\"'@general:actions.apply.title'\"\n [iconOnly]=\"true\"\n #filterButton\n [color]=\"'primary'\"\n (onClick)=\"applyFilters()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-search\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (vm.activeListLayout() === 'table') {\n <div>\n <ax-button id=\"axp-toolbar-btn-columns\" [iconOnly]=\"true\" #columnButton [color]=\"'default'\">\n <i class=\"fa-light fa-table-columns\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-columns\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"columnButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #columnsPopover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.common.columns' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-4\">\n <axp-query-columns [columns]=\"vm.columns()\" (columnsChange)=\"onColumnsChange($event)\"></axp-query-columns>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n\n @if (vm.canSort()) {\n <div>\n <ax-button id=\"axp-toolbar-btn-sort\" [iconOnly]=\"true\" [text]=\"'Sorts'\" #sortButton [color]=\"'default'\">\n <i class=\"fa-light fa-sort-amount-up\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-sort\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"sortButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #popover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.common.sorts' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-4\">\n <!-- <axp-list-view-option-sorting [viewModel]=\"vm\"></axp-list-view-option-sorting> -->\n <axp-query-sorts\n [sortDefinitions]=\"sortDefinitions()\"\n (sortQueriesChange)=\"onSortQueriesChange($event)\"\n [initialSortQueries]=\"sortQueries()\"\n ></axp-query-sorts>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n @if (deviceService.isSmall()) {\n <ax-button\n id=\"axp-toolbar-btn-category\"\n (onClick)=\"parent.toggleCategoryDrawer()\"\n [iconOnly]=\"true\"\n [color]=\"'default'\"\n >\n <i class=\"fa-light fa-bars\"></i>\n </ax-button>\n }\n\n @if (showListDisplayModeControl()) {\n <div>\n <ax-button id=\"axp-toolbar-btn-list-display\" [iconOnly]=\"true\" #listDisplayButton [color]=\"'default'\">\n <i [class]=\"listDisplayModeButtonIcon()\"></i>\n </ax-button>\n <ax-popover\n id=\"axp-popover-list-display\"\n [adaptivityEnabled]=\"true\"\n [offsetY]=\"10\"\n [target]=\"listDisplayButton\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n #listDisplayModePopover\n >\n <div class=\"ax-lightest-surface ax-shadow-md ax-border md:ax-w-72 ax-w-full ax-rounded-md\">\n <ax-header class=\"ax-border-b ax-lighter-surface ax-rounded-t-md ax-p-4 ax-font-bold\">\n {{ '@general:terms.interface.list-display' | translate | async }}\n </ax-header>\n <div class=\"ax-py-2 ax-px-2\">\n <ax-button-item-list>\n @if (hasTableListDisplayOption()) {\n <ax-button-item\n [text]=\"('@general:terms.interface.table-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'table'\"\n (onClick)=\"setListDisplayMode('table')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-table\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n @if (hasTableListDisplayOption()) {\n <ax-divider />\n }\n <ax-button-item\n [text]=\"('@general:terms.interface.card-compact-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'card-compact'\"\n (onClick)=\"setListDisplayMode('card-compact')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-grip-lines\"></i>\n </ax-prefix>\n </ax-button-item>\n <ax-button-item\n [text]=\"('@general:terms.interface.card-expanded-view' | translate | async)!\"\n [selected]=\"listDisplayMode() === 'card-expanded'\"\n (onClick)=\"setListDisplayMode('card-expanded')\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-grid-2\"></i>\n </ax-prefix>\n </ax-button-item>\n </ax-button-item-list>\n </div>\n </div>\n </ax-popover>\n </div>\n }\n </div>\n</div>\n" }]
1503
+ }], propDecorators: { vm: [{
1504
+ type: Input,
1505
+ args: ['viewModel']
1506
+ }], listDisplayModePopover: [{ type: i0.ViewChild, args: ['listDisplayModePopover', { isSignal: true }] }] } });
1507
+
1508
+ class AXPEntityMasterListCardItemSkeletonComponent {
1509
+ constructor() {
1510
+ //#region ---- Inputs ----
1511
+ this.fieldCount = input(4, ...(ngDevMode ? [{ debugName: "fieldCount" }] : /* istanbul ignore next */ []));
1512
+ this.badgeCount = input(1, ...(ngDevMode ? [{ debugName: "badgeCount" }] : /* istanbul ignore next */ []));
1513
+ this.showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : /* istanbul ignore next */ []));
1514
+ this.showHeaderMenu = input(false, ...(ngDevMode ? [{ debugName: "showHeaderMenu" }] : /* istanbul ignore next */ []));
1515
+ this.showContent = input(true, ...(ngDevMode ? [{ debugName: "showContent" }] : /* istanbul ignore next */ []));
1516
+ //#endregion
1517
+ //#region ---- Computed ----
1518
+ this.fieldPlaceholders = computed(() => Array.from({ length: Math.max(this.fieldCount(), 0) }, (_, index) => index), ...(ngDevMode ? [{ debugName: "fieldPlaceholders" }] : /* istanbul ignore next */ []));
1519
+ this.badgePlaceholders = computed(() => Array.from({ length: Math.max(this.badgeCount(), 0) }, (_, index) => index), ...(ngDevMode ? [{ debugName: "badgePlaceholders" }] : /* istanbul ignore next */ []));
1520
+ }
1521
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardItemSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1522
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterListCardItemSkeletonComponent, isStandalone: true, selector: "axp-entity-master-list-card-item-skeleton", inputs: { fieldCount: { classPropertyName: "fieldCount", publicName: "fieldCount", isSignal: true, isRequired: false, transformFunction: null }, badgeCount: { classPropertyName: "badgeCount", publicName: "badgeCount", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, showHeaderMenu: { classPropertyName: "showHeaderMenu", publicName: "showHeaderMenu", isSignal: true, isRequired: false, transformFunction: null }, showContent: { classPropertyName: "showContent", publicName: "showContent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<article class=\"axp-entity-card-skeleton\" aria-hidden=\"true\">\n <div class=\"axp-entity-card-skeleton__header\">\n <div class=\"axp-entity-card-skeleton__header-main\">\n <div class=\"axp-entity-card-skeleton__header-text\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__title\"></ax-skeleton>\n @if (showContent()) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__description\"></ax-skeleton>\n }\n </div>\n @for (index of badgePlaceholders(); track index) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__badge\"></ax-skeleton>\n }\n </div>\n @if (showHeaderMenu()) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__menu\"></ax-skeleton>\n }\n </div>\n\n @if (showContent()) {\n @if (fieldPlaceholders().length) {\n <div class=\"axp-entity-card-skeleton__fields\">\n @for (index of fieldPlaceholders(); track index) {\n <div class=\"axp-entity-card-skeleton__field-row\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__field-label\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__field-value\"></ax-skeleton>\n </div>\n }\n </div>\n }\n }\n\n @if (showFooter()) {\n <footer class=\"axp-entity-card-skeleton__footer\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__primary-action\"></ax-skeleton>\n </footer>\n }\n</article>\n", styles: [":host{display:block;height:auto;width:100%}.axp-entity-card-skeleton{display:flex;height:auto;width:100%;flex-direction:column;gap:1rem;border-radius:.75rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));padding:1rem}.axp-entity-card-skeleton__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.axp-entity-card-skeleton__header-main{display:flex;min-width:0px;flex:1 1 0%;flex-wrap:wrap;align-items:flex-start;gap:.5rem}.axp-entity-card-skeleton__header-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}.axp-entity-card-skeleton__title{height:1.25rem;width:10rem;max-width:100%;border-radius:.25rem}.axp-entity-card-skeleton__badge{height:1.25rem;width:3.5rem;flex-shrink:0;border-radius:9999px}.axp-entity-card-skeleton__menu{height:2rem;width:2rem;flex-shrink:0;border-radius:.25rem}.axp-entity-card-skeleton__description{height:.875rem;width:8rem;max-width:100%;border-radius:.25rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.axp-entity-card-skeleton__fields{display:flex;flex-direction:column;gap:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));padding-top:1rem}.axp-entity-card-skeleton__field-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem}.axp-entity-card-skeleton__field-label{height:1rem;width:6rem;border-radius:.25rem}.axp-entity-card-skeleton__field-value{height:1rem;width:7rem;border-radius:.25rem}.axp-entity-card-skeleton__footer{margin-left:-1rem;margin-right:-1rem;margin-top:auto;margin-bottom:-1rem;border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.75rem 1rem}.axp-entity-card-skeleton__primary-action{height:2.5rem;width:100%;border-radius:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i2$2.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1523
+ }
1524
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardItemSkeletonComponent, decorators: [{
1525
+ type: Component,
1526
+ args: [{ selector: 'axp-entity-master-list-card-item-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXSkeletonModule], template: "<article class=\"axp-entity-card-skeleton\" aria-hidden=\"true\">\n <div class=\"axp-entity-card-skeleton__header\">\n <div class=\"axp-entity-card-skeleton__header-main\">\n <div class=\"axp-entity-card-skeleton__header-text\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__title\"></ax-skeleton>\n @if (showContent()) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__description\"></ax-skeleton>\n }\n </div>\n @for (index of badgePlaceholders(); track index) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__badge\"></ax-skeleton>\n }\n </div>\n @if (showHeaderMenu()) {\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__menu\"></ax-skeleton>\n }\n </div>\n\n @if (showContent()) {\n @if (fieldPlaceholders().length) {\n <div class=\"axp-entity-card-skeleton__fields\">\n @for (index of fieldPlaceholders(); track index) {\n <div class=\"axp-entity-card-skeleton__field-row\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__field-label\"></ax-skeleton>\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__field-value\"></ax-skeleton>\n </div>\n }\n </div>\n }\n }\n\n @if (showFooter()) {\n <footer class=\"axp-entity-card-skeleton__footer\">\n <ax-skeleton [animated]=\"true\" class=\"axp-entity-card-skeleton__primary-action\"></ax-skeleton>\n </footer>\n }\n</article>\n", styles: [":host{display:block;height:auto;width:100%}.axp-entity-card-skeleton{display:flex;height:auto;width:100%;flex-direction:column;gap:1rem;border-radius:.75rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));padding:1rem}.axp-entity-card-skeleton__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.axp-entity-card-skeleton__header-main{display:flex;min-width:0px;flex:1 1 0%;flex-wrap:wrap;align-items:flex-start;gap:.5rem}.axp-entity-card-skeleton__header-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}.axp-entity-card-skeleton__title{height:1.25rem;width:10rem;max-width:100%;border-radius:.25rem}.axp-entity-card-skeleton__badge{height:1.25rem;width:3.5rem;flex-shrink:0;border-radius:9999px}.axp-entity-card-skeleton__menu{height:2rem;width:2rem;flex-shrink:0;border-radius:.25rem}.axp-entity-card-skeleton__description{height:.875rem;width:8rem;max-width:100%;border-radius:.25rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.axp-entity-card-skeleton__fields{display:flex;flex-direction:column;gap:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));padding-top:1rem}.axp-entity-card-skeleton__field-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem}.axp-entity-card-skeleton__field-label{height:1rem;width:6rem;border-radius:.25rem}.axp-entity-card-skeleton__field-value{height:1rem;width:7rem;border-radius:.25rem}.axp-entity-card-skeleton__footer{margin-left:-1rem;margin-right:-1rem;margin-top:auto;margin-bottom:-1rem;border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.75rem 1rem}.axp-entity-card-skeleton__primary-action{height:2.5rem;width:100%;border-radius:.5rem}\n"] }]
1527
+ }], propDecorators: { fieldCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldCount", required: false }] }], badgeCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeCount", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], showHeaderMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeaderMenu", required: false }] }], showContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "showContent", required: false }] }] } });
1528
+
1529
+ class AXPEntityMasterListCardItemComponent {
1530
+ //#endregion
1531
+ //#region ---- Lifecycle ----
1532
+ constructor() {
1533
+ this.translation = inject(AXTranslationService);
1534
+ //#region ---- Inputs & Outputs ----
1535
+ this.vm = input.required(...(ngDevMode ? [{ debugName: "vm" }] : /* istanbul ignore next */ []));
1536
+ this.row = input.required(...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
1537
+ this.selected = input(false, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
1538
+ this.showNewBadge = input(false, ...(ngDevMode ? [{ debugName: "showNewBadge" }] : /* istanbul ignore next */ []));
1539
+ this.showSelection = input(false, ...(ngDevMode ? [{ debugName: "showSelection" }] : /* istanbul ignore next */ []));
1540
+ this.selectionChange = output();
1541
+ this.commandClick = output();
1542
+ //#endregion
1543
+ //#region ---- Computed ----
1544
+ this.header = computed(() => this.vm().cardLayout()?.header, ...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
1545
+ this.showCardContent = computed(() => this.vm().cardContentExpanded(), ...(ngDevMode ? [{ debugName: "showCardContent" }] : /* istanbul ignore next */ []));
1546
+ this.headerBadgeFields = computed(() => this.vm()
1547
+ .cardFields()
1548
+ .filter((field) => field.visible !== false && field.isBadgeDisplay), ...(ngDevMode ? [{ debugName: "headerBadgeFields" }] : /* istanbul ignore next */ []));
1549
+ /** Column name to omit from body when `header.title` is a simple property path (not an expression). */
1550
+ this.headerTitleColumnName = computed(() => {
1551
+ const title = this.header()?.title;
1552
+ if (!title || title.includes('{{')) {
1553
+ return undefined;
1554
+ }
1555
+ return title;
1556
+ }, ...(ngDevMode ? [{ debugName: "headerTitleColumnName" }] : /* istanbul ignore next */ []));
1557
+ this.bodyFields = computed(() => {
1558
+ const titleColumn = this.headerTitleColumnName();
1559
+ return this.vm()
1560
+ .cardFields()
1561
+ .filter((field) => field.visible !== false && !field.isBadgeDisplay && field.name !== titleColumn);
1562
+ }, ...(ngDevMode ? [{ debugName: "bodyFields" }] : /* istanbul ignore next */ []));
1563
+ this.badgeColorByField = signal({}, ...(ngDevMode ? [{ debugName: "badgeColorByField" }] : /* istanbul ignore next */ []));
1564
+ this.titleText = signal('', ...(ngDevMode ? [{ debugName: "titleText" }] : /* istanbul ignore next */ []));
1565
+ this.descriptionText = signal('', ...(ngDevMode ? [{ debugName: "descriptionText" }] : /* istanbul ignore next */ []));
1566
+ this.iconClass = signal(null, ...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
1567
+ this.primaryActions = computed(() => this.vm().primaryRowActions(), ...(ngDevMode ? [{ debugName: "primaryActions" }] : /* istanbul ignore next */ []));
1568
+ this.primaryActionsIconOnly = computed(() => !this.vm().cardContentExpanded(), ...(ngDevMode ? [{ debugName: "primaryActionsIconOnly" }] : /* istanbul ignore next */ []));
1569
+ this.secondaryActions = signal([], ...(ngDevMode ? [{ debugName: "secondaryActions" }] : /* istanbul ignore next */ []));
1570
+ effect(() => {
1571
+ const row = this.row();
1572
+ const header = this.header();
1573
+ const badgeFields = this.headerBadgeFields().filter((field) => !this.isStatusBadgeField(field));
1574
+ untracked(() => {
1575
+ void this.loadSecondaryActions(row);
1576
+ void this.resolveHeaderFields(row, header);
1577
+ void this.resolveBadgeColors(row, badgeFields);
1578
+ });
1579
+ });
1580
+ }
1581
+ //#endregion
1582
+ //#region ---- Row Actions ----
1583
+ async loadSecondaryActions(row) {
1584
+ const actions = await this.vm().cardSecondaryRowActions(row);
1585
+ this.secondaryActions.set(actions);
1586
+ }
1587
+ //#endregion
1588
+ //#region ---- UI Handlers ----
1589
+ onCardClick(event) {
1590
+ if (event.ctrlKey || event.metaKey) {
1591
+ event.preventDefault();
1592
+ this.selectionChange.emit(!this.selected());
1593
+ }
1594
+ }
1595
+ onCardDblClick(event) {
1596
+ event.preventDefault();
1597
+ void this.runDefaultAction();
1598
+ }
1599
+ onCardKeydown(event) {
1600
+ if (event.key === 'Enter' || event.key === ' ') {
1601
+ event.preventDefault();
1602
+ void this.runDefaultAction();
1603
+ }
1604
+ }
1605
+ onPrimaryActionClick(actionName, event) {
1606
+ event.nativeEvent?.stopPropagation();
1607
+ const action = this.primaryActions().find((a) => a.name === actionName);
1608
+ if (action && !action.disabled) {
1609
+ this.commandClick.emit(action.name);
1610
+ }
1611
+ }
1612
+ onSecondaryActionClick(item, event) {
1613
+ event.nativeEvent?.stopPropagation();
1614
+ if (item.isCardSelect || item.name === AXPEntityMasterListCardSelectActionName) {
1615
+ if (!this.selected()) {
1616
+ this.selectionChange.emit(true);
1617
+ }
1618
+ return;
1619
+ }
1620
+ this.commandClick.emit(item.name);
1621
+ }
1622
+ onSelectionCheckboxChange(event) {
1623
+ this.selectionChange.emit(!!event.value);
1624
+ }
1625
+ onSelectionCheckboxClick(event) {
1626
+ event.stopPropagation();
1627
+ }
1628
+ stopClickPropagation(event) {
1629
+ event.nativeEvent?.stopPropagation();
1630
+ }
1631
+ isStatusBadgeField(field) {
1632
+ return field.isStatusWidget;
1633
+ }
1634
+ resolveBadgeText(field) {
1635
+ const path = field.node().path ?? field.name;
1636
+ const value = get(this.row(), path);
1637
+ if (value == null) {
1638
+ return '';
1639
+ }
1640
+ if (typeof value === 'object') {
1641
+ const record = value;
1642
+ const label = record['title'] ?? record['name'] ?? record['label'];
1643
+ return label == null ? '' : String(label);
1644
+ }
1645
+ return String(value);
1646
+ }
1647
+ resolveBadgeColor(field) {
1648
+ return this.badgeColorByField()[field.name] ?? 'default';
1649
+ }
1650
+ //#endregion
1651
+ //#region ---- Private Helpers ----
1652
+ async resolveBadgeColors(row, fields) {
1653
+ const vm = this.vm();
1654
+ const entries = await Promise.all(fields.map(async (field) => [field.name, await vm.resolveCardBadgeColor(field, row)]));
1655
+ this.badgeColorByField.set(Object.fromEntries(entries));
1656
+ }
1657
+ async resolveHeaderFields(row, header) {
1658
+ const vm = this.vm();
1659
+ const [title, description, icon] = await Promise.all([
1660
+ vm.resolveCardHeaderValue(header?.title, row),
1661
+ vm.resolveCardHeaderValue(header?.description, row),
1662
+ vm.resolveCardHeaderValue(header?.icon, row),
1663
+ ]);
1664
+ this.titleText.set(title);
1665
+ this.descriptionText.set(description);
1666
+ this.iconClass.set(this.toIconClass(this.toIconString(icon)));
1667
+ }
1668
+ toIconString(value) {
1669
+ if (!value) {
1670
+ return '';
1671
+ }
1672
+ if (typeof value === 'string') {
1673
+ return value;
1674
+ }
1675
+ if (this.translation.isValidMultiLanguageObject(value)) {
1676
+ return this.translation.resolve(value);
1677
+ }
1678
+ return '';
1679
+ }
1680
+ toIconClass(value) {
1681
+ if (!value) {
1682
+ return null;
1683
+ }
1684
+ return value.includes('fa-') ? value : `fa-light fa-${value}`;
1685
+ }
1686
+ async runDefaultAction() {
1687
+ const primary = this.primaryActions();
1688
+ const secondary = this.secondaryActions().filter((a) => !a.isCardSelect);
1689
+ const all = [...primary, ...secondary].filter(Boolean);
1690
+ const defaultAction = all.find((a) => {
1691
+ if (!a) {
1692
+ return false;
1693
+ }
1694
+ const commandName = a.name.split('&')[0];
1695
+ return (a.default || commandName === 'open-entity') && !a.disabled;
1696
+ });
1697
+ if (defaultAction) {
1698
+ this.commandClick.emit(defaultAction.name);
1699
+ }
1700
+ }
1701
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1702
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterListCardItemComponent, isStandalone: true, selector: "axp-entity-master-list-card-item", inputs: { vm: { classPropertyName: "vm", publicName: "vm", isSignal: true, isRequired: true, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, showNewBadge: { classPropertyName: "showNewBadge", publicName: "showNewBadge", isSignal: true, isRequired: false, transformFunction: null }, showSelection: { classPropertyName: "showSelection", publicName: "showSelection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", commandClick: "commandClick" }, ngImport: i0, template: "<article\n class=\"axp-entity-card\"\n role=\"button\"\n tabindex=\"0\"\n [class.axp-entity-card--selected]=\"selected()\"\n [class.axp-entity-card--new]=\"showNewBadge()\"\n [attr.aria-selected]=\"selected()\"\n (click)=\"onCardClick($event)\"\n (dblclick)=\"onCardDblClick($event)\"\n (keydown)=\"onCardKeydown($event)\"\n>\n <div class=\"axp-entity-card__header\">\n <div class=\"axp-entity-card__header-main\">\n <div class=\"axp-entity-card__header-text\">\n <div class=\"axp-entity-card__title-row\">\n @if (iconClass(); as icon) {\n <i [class]=\"icon + ' axp-entity-card__icon'\" aria-hidden=\"true\"></i>\n }\n\n <h3 class=\"axp-entity-card__title\" title=\"{{ titleText() | translate | async }}\">\n {{ titleText() | translate | async }}\n </h3>\n\n @if (showNewBadge()) {\n <ax-badge\n class=\"axp-entity-card__header-badge\"\n [text]=\"('@acorex:dataTable.rowHighlight.new' | translate | async) || ''\"\n color=\"success\"\n />\n }\n\n @for (field of headerBadgeFields(); track field.name) {\n @if (isStatusBadgeField(field)) {\n <div class=\"axp-entity-card__header-badge\">\n <axp-widget-column-cell [node]=\"field.headerBadgeNode()\" [rowData]=\"row()\" [caption]=\"field.title\" />\n </div>\n } @else if (resolveBadgeText(field)) {\n <ax-badge\n class=\"axp-entity-card__header-badge\"\n [text]=\"resolveBadgeText(field)\"\n [color]=\"resolveBadgeColor(field)\"\n />\n }\n }\n </div>\n\n @if (descriptionText()) {\n <p class=\"axp-entity-card__description\" title=\"{{ descriptionText() | translate | async }}\">\n {{ descriptionText() | translate | async }}\n </p>\n }\n </div>\n </div>\n\n @if (showSelection() || secondaryActions().length) {\n <div class=\"axp-entity-card__header-actions\">\n @if (showSelection()) {\n <ax-check-box\n class=\"axp-entity-card__select-checkbox\"\n [value]=\"selected()\"\n (onValueChanged)=\"onSelectionCheckboxChange($event)\"\n (click)=\"onSelectionCheckboxClick($event)\"\n />\n } @else if (secondaryActions().length) {\n <ax-button [look]=\"'blank'\" [color]=\"'default'\" (onClick)=\"stopClickPropagation($event)\">\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis\" aria-hidden=\"true\"></i>\n </ax-prefix>\n\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (item of secondaryActions(); track $index) {\n @if (item.divided) {\n <ax-divider />\n }\n <ax-button-item\n [text]=\"(item.title | translate | async)!\"\n [color]=\"item.color\"\n [disabled]=\"item.disabled\"\n (onClick)=\"onSecondaryActionClick(item, $event)\"\n >\n @if (item.icon) {\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ item.icon }}\"></ax-icon>\n </ax-prefix>\n }\n </ax-button-item>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n </div>\n }\n </div>\n\n @if (showCardContent()) {\n @if (bodyFields().length) {\n <div class=\"axp-entity-card__fields ax-grid ax-grid-cols-12 ax-gap-y-3 ax-gap-x-4\">\n @for (field of bodyFields(); track field.name) {\n <div\n class=\"axp-entity-card__field ax-col-span-12\"\n [axp-grid-layout]=\"field.layout ?? { positions: { sm: { colSpan: 12 } } }\"\n >\n <div class=\"axp-entity-card__field-row\">\n <span class=\"axp-entity-card__field-label\">{{ field.title | translate | async }}</span>\n\n <div class=\"axp-entity-card__field-value\">\n <axp-widget-column-cell [node]=\"field.node()\" [rowData]=\"row()\" [caption]=\"field.title\" />\n </div>\n </div>\n </div>\n }\n </div>\n }\n }\n\n @if (primaryActions().length) {\n <footer class=\"axp-entity-card__footer\">\n @for (action of primaryActions(); track $index) {\n <ax-button\n class=\"axp-entity-card__primary-action ax-text-xs\"\n [class.ax-flex-1]=\"!primaryActionsIconOnly()\"\n [look]=\"'outline'\"\n [color]=\"action.color\"\n [size]=\"'sm'\"\n [iconOnly]=\"primaryActionsIconOnly()\"\n [disabled]=\"action.disabled\"\n [text]=\"(action.title | translate | async)!\"\n (onClick)=\"onPrimaryActionClick(action.name, $event)\"\n >\n @if (action.icon) {\n <ax-prefix>\n <i class=\"fa-light {{ action.icon }}\"></i>\n </ax-prefix>\n }\n </ax-button>\n }\n </footer>\n }\n</article>\n", styles: [":host{display:block;height:auto;width:100%}.axp-entity-card{display:flex;height:auto;width:100%;flex-direction:column;gap:1rem;border-radius:.75rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));padding:1rem;cursor:pointer;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.axp-entity-card:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.axp-entity-card:focus-visible{outline-width:2px;outline-offset:2px;outline-color:rgba(var(--ax-sys-color-primary-500),1)}.axp-entity-card--selected{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1));--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);box-shadow:0 0 0 1px rgb(var(--ax-sys-color-primary-500)/.25),0 2px 6px #0000000f}.axp-entity-card--new{border-color:rgb(var(--ax-sys-color-border-success-surface));box-shadow:0 0 0 1px rgb(var(--ax-sys-color-success-500)/.2)}.axp-entity-card__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.axp-entity-card__header-main{display:flex;min-width:0px;flex:1 1 0%;flex-wrap:wrap;align-items:center;gap:.5rem}.axp-entity-card__header-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}.axp-entity-card__title-row{display:flex;min-width:0px;align-items:center;gap:.5rem}.axp-entity-card__select-checkbox,.axp-entity-card__header-badge{flex-shrink:0}.axp-entity-card__header-actions{display:flex;flex-shrink:0;align-items:center;gap:.125rem}.axp-entity-card__title{min-width:0px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:1.125rem;line-height:1.75rem;font-weight:700;line-height:1.25}.axp-entity-card__icon{flex-shrink:0;font-size:1.125rem;line-height:1.75rem;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.axp-entity-card__description{min-width:0px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;line-height:1.375;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1))}.axp-entity-card__fields{border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));padding-top:1rem}.axp-entity-card__field-row{display:grid;width:100%;align-items:center;gap:.75rem;grid-template-columns:auto minmax(0,1fr)}.axp-entity-card__field-label{flex-shrink:0;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.axp-entity-card__field-value{min-width:0px;max-width:100%;justify-self:end;text-align:end;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1));width:fit-content}.axp-entity-card__field-value axp-widget-column-cell{display:block;max-width:100%}.axp-entity-card__footer{margin-left:-1rem;margin-right:-1rem;margin-top:auto;margin-bottom:-1rem;display:flex;align-items:center;justify-content:flex-end;gap:1rem;border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.75rem 1rem}.axp-entity-card__primary-action{min-width:0px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "component", type: i3$3.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i5.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: AXPGridLayoutDirective, selector: "[axp-grid-layout]", inputs: ["axp-grid-layout"] }, { kind: "component", type: AXPWidgetColumnCellComponent, selector: "axp-widget-column-cell", inputs: ["node", "rowData", "caption"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1703
+ }
1704
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardItemComponent, decorators: [{
1705
+ type: Component,
1706
+ args: [{ selector: 'axp-entity-master-list-card-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1707
+ CommonModule,
1708
+ AXBadgeModule,
1709
+ AXButtonModule,
1710
+ AXCheckBoxModule,
1711
+ AXDecoratorModule,
1712
+ AXDropdownModule,
1713
+ AXTranslationModule,
1714
+ AXPGridLayoutDirective,
1715
+ AXPWidgetColumnCellComponent,
1716
+ ], template: "<article\n class=\"axp-entity-card\"\n role=\"button\"\n tabindex=\"0\"\n [class.axp-entity-card--selected]=\"selected()\"\n [class.axp-entity-card--new]=\"showNewBadge()\"\n [attr.aria-selected]=\"selected()\"\n (click)=\"onCardClick($event)\"\n (dblclick)=\"onCardDblClick($event)\"\n (keydown)=\"onCardKeydown($event)\"\n>\n <div class=\"axp-entity-card__header\">\n <div class=\"axp-entity-card__header-main\">\n <div class=\"axp-entity-card__header-text\">\n <div class=\"axp-entity-card__title-row\">\n @if (iconClass(); as icon) {\n <i [class]=\"icon + ' axp-entity-card__icon'\" aria-hidden=\"true\"></i>\n }\n\n <h3 class=\"axp-entity-card__title\" title=\"{{ titleText() | translate | async }}\">\n {{ titleText() | translate | async }}\n </h3>\n\n @if (showNewBadge()) {\n <ax-badge\n class=\"axp-entity-card__header-badge\"\n [text]=\"('@acorex:dataTable.rowHighlight.new' | translate | async) || ''\"\n color=\"success\"\n />\n }\n\n @for (field of headerBadgeFields(); track field.name) {\n @if (isStatusBadgeField(field)) {\n <div class=\"axp-entity-card__header-badge\">\n <axp-widget-column-cell [node]=\"field.headerBadgeNode()\" [rowData]=\"row()\" [caption]=\"field.title\" />\n </div>\n } @else if (resolveBadgeText(field)) {\n <ax-badge\n class=\"axp-entity-card__header-badge\"\n [text]=\"resolveBadgeText(field)\"\n [color]=\"resolveBadgeColor(field)\"\n />\n }\n }\n </div>\n\n @if (descriptionText()) {\n <p class=\"axp-entity-card__description\" title=\"{{ descriptionText() | translate | async }}\">\n {{ descriptionText() | translate | async }}\n </p>\n }\n </div>\n </div>\n\n @if (showSelection() || secondaryActions().length) {\n <div class=\"axp-entity-card__header-actions\">\n @if (showSelection()) {\n <ax-check-box\n class=\"axp-entity-card__select-checkbox\"\n [value]=\"selected()\"\n (onValueChanged)=\"onSelectionCheckboxChange($event)\"\n (click)=\"onSelectionCheckboxClick($event)\"\n />\n } @else if (secondaryActions().length) {\n <ax-button [look]=\"'blank'\" [color]=\"'default'\" (onClick)=\"stopClickPropagation($event)\">\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis\" aria-hidden=\"true\"></i>\n </ax-prefix>\n\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (item of secondaryActions(); track $index) {\n @if (item.divided) {\n <ax-divider />\n }\n <ax-button-item\n [text]=\"(item.title | translate | async)!\"\n [color]=\"item.color\"\n [disabled]=\"item.disabled\"\n (onClick)=\"onSecondaryActionClick(item, $event)\"\n >\n @if (item.icon) {\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ item.icon }}\"></ax-icon>\n </ax-prefix>\n }\n </ax-button-item>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n </div>\n }\n </div>\n\n @if (showCardContent()) {\n @if (bodyFields().length) {\n <div class=\"axp-entity-card__fields ax-grid ax-grid-cols-12 ax-gap-y-3 ax-gap-x-4\">\n @for (field of bodyFields(); track field.name) {\n <div\n class=\"axp-entity-card__field ax-col-span-12\"\n [axp-grid-layout]=\"field.layout ?? { positions: { sm: { colSpan: 12 } } }\"\n >\n <div class=\"axp-entity-card__field-row\">\n <span class=\"axp-entity-card__field-label\">{{ field.title | translate | async }}</span>\n\n <div class=\"axp-entity-card__field-value\">\n <axp-widget-column-cell [node]=\"field.node()\" [rowData]=\"row()\" [caption]=\"field.title\" />\n </div>\n </div>\n </div>\n }\n </div>\n }\n }\n\n @if (primaryActions().length) {\n <footer class=\"axp-entity-card__footer\">\n @for (action of primaryActions(); track $index) {\n <ax-button\n class=\"axp-entity-card__primary-action ax-text-xs\"\n [class.ax-flex-1]=\"!primaryActionsIconOnly()\"\n [look]=\"'outline'\"\n [color]=\"action.color\"\n [size]=\"'sm'\"\n [iconOnly]=\"primaryActionsIconOnly()\"\n [disabled]=\"action.disabled\"\n [text]=\"(action.title | translate | async)!\"\n (onClick)=\"onPrimaryActionClick(action.name, $event)\"\n >\n @if (action.icon) {\n <ax-prefix>\n <i class=\"fa-light {{ action.icon }}\"></i>\n </ax-prefix>\n }\n </ax-button>\n }\n </footer>\n }\n</article>\n", styles: [":host{display:block;height:auto;width:100%}.axp-entity-card{display:flex;height:auto;width:100%;flex-direction:column;gap:1rem;border-radius:.75rem;border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));padding:1rem;cursor:pointer;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.axp-entity-card:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.axp-entity-card:focus-visible{outline-width:2px;outline-offset:2px;outline-color:rgba(var(--ax-sys-color-primary-500),1)}.axp-entity-card--selected{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1));--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);box-shadow:0 0 0 1px rgb(var(--ax-sys-color-primary-500)/.25),0 2px 6px #0000000f}.axp-entity-card--new{border-color:rgb(var(--ax-sys-color-border-success-surface));box-shadow:0 0 0 1px rgb(var(--ax-sys-color-success-500)/.2)}.axp-entity-card__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.axp-entity-card__header-main{display:flex;min-width:0px;flex:1 1 0%;flex-wrap:wrap;align-items:center;gap:.5rem}.axp-entity-card__header-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}.axp-entity-card__title-row{display:flex;min-width:0px;align-items:center;gap:.5rem}.axp-entity-card__select-checkbox,.axp-entity-card__header-badge{flex-shrink:0}.axp-entity-card__header-actions{display:flex;flex-shrink:0;align-items:center;gap:.125rem}.axp-entity-card__title{min-width:0px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:1.125rem;line-height:1.75rem;font-weight:700;line-height:1.25}.axp-entity-card__icon{flex-shrink:0;font-size:1.125rem;line-height:1.75rem;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.axp-entity-card__description{min-width:0px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;line-height:1.375;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1))}.axp-entity-card__fields{border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));padding-top:1rem}.axp-entity-card__field-row{display:grid;width:100%;align-items:center;gap:.75rem;grid-template-columns:auto minmax(0,1fr)}.axp-entity-card__field-label{flex-shrink:0;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.axp-entity-card__field-value{min-width:0px;max-width:100%;justify-self:end;text-align:end;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1));width:fit-content}.axp-entity-card__field-value axp-widget-column-cell{display:block;max-width:100%}.axp-entity-card__footer{margin-left:-1rem;margin-right:-1rem;margin-top:auto;margin-bottom:-1rem;display:flex;align-items:center;justify-content:flex-end;gap:1rem;border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem;border-top-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.75rem 1rem}.axp-entity-card__primary-action{min-width:0px}\n"] }]
1717
+ }], ctorParameters: () => [], propDecorators: { vm: [{ type: i0.Input, args: [{ isSignal: true, alias: "vm", required: true }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], showNewBadge: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNewBadge", required: false }] }], showSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelection", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], commandClick: [{ type: i0.Output, args: ["commandClick"] }] } });
1718
+
1719
+ class AXPEntityMasterListCardListComponent {
1720
+ //#endregion
1721
+ //#region ---- Lifecycle ----
1722
+ constructor() {
1723
+ //#region ---- Services & Dependencies ----
1724
+ this.deviceService = inject(AXPDeviceService);
1725
+ //#endregion
1726
+ //#region ---- Inputs & Outputs ----
1727
+ this.vm = input.required(...(ngDevMode ? [{ debugName: "vm" }] : /* istanbul ignore next */ []));
1728
+ this.pageChanged = output();
1729
+ //#endregion
1730
+ //#region ---- State ----
1731
+ this.pagerDisplayMode = computed(() => (this.deviceService.isSmall() ? 'compact' : 'full'), ...(ngDevMode ? [{ debugName: "pagerDisplayMode" }] : /* istanbul ignore next */ []));
1732
+ this.items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1733
+ this.loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1734
+ this.highlightedRowId = signal(null, ...(ngDevMode ? [{ debugName: "highlightedRowId" }] : /* istanbul ignore next */ []));
1735
+ this.totalCount = signal(0, ...(ngDevMode ? [{ debugName: "totalCount" }] : /* istanbul ignore next */ []));
1736
+ this.pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
1737
+ this.pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
1738
+ this.showSelection = computed(() => this.vm().selectedItems().length > 0, ...(ngDevMode ? [{ debugName: "showSelection" }] : /* istanbul ignore next */ []));
1739
+ this.rowKey = computed(() => this.vm().dataSource.config?.key ?? 'id', ...(ngDevMode ? [{ debugName: "rowKey" }] : /* istanbul ignore next */ []));
1740
+ this.skeletonCount = computed(() => Math.max(this.pageSize(), 1), ...(ngDevMode ? [{ debugName: "skeletonCount" }] : /* istanbul ignore next */ []));
1741
+ this.skeletonFieldCount = computed(() => {
1742
+ if (!this.vm().cardContentExpanded()) {
1743
+ return 0;
1744
+ }
1745
+ const bodyCount = this.vm()
1746
+ .cardFields()
1747
+ .filter((field) => field.visible !== false && !field.isBadgeDisplay).length;
1748
+ return bodyCount > 0 ? bodyCount : 4;
1749
+ }, ...(ngDevMode ? [{ debugName: "skeletonFieldCount" }] : /* istanbul ignore next */ []));
1750
+ this.skeletonBadgeCount = computed(() => {
1751
+ const count = this.vm()
1752
+ .cardFields()
1753
+ .filter((field) => field.visible !== false && field.isBadgeDisplay).length;
1754
+ return count > 0 ? count : 1;
1755
+ }, ...(ngDevMode ? [{ debugName: "skeletonBadgeCount" }] : /* istanbul ignore next */ []));
1756
+ this.skeletonShowFooter = computed(() => this.vm().primaryRowActions().length > 0, ...(ngDevMode ? [{ debugName: "skeletonShowFooter" }] : /* istanbul ignore next */ []));
1757
+ this.skeletonPlaceholders = computed(() => Array.from({ length: this.skeletonCount() }, (_, index) => index), ...(ngDevMode ? [{ debugName: "skeletonPlaceholders" }] : /* istanbul ignore next */ []));
1758
+ effect((onCleanup) => {
1759
+ const vm = this.vm();
1760
+ const dataSource = vm.dataSource;
1761
+ untracked(() => {
1762
+ this.syncPagingFromDataSource(vm);
1763
+ this.syncFromDataSource();
1764
+ });
1765
+ const changedSub = dataSource.onChanged.subscribe(() => {
1766
+ this.syncFromDataSource();
1767
+ });
1768
+ const loadingSub = dataSource.onLoadingChanged.subscribe((isLoading) => {
1769
+ this.loading.set(isLoading);
1770
+ });
1771
+ onCleanup(() => {
1772
+ changedSub.unsubscribe();
1773
+ loadingSub.unsubscribe();
1774
+ });
1775
+ });
1776
+ }
1777
+ //#endregion
1778
+ //#region ---- Public API ----
1779
+ async reload(resetPagination = false) {
1780
+ if (!resetPagination) {
1781
+ await this.vm().ensureListPagingResolved();
1782
+ this.vm().applyPagingToDataSourceWithoutLoad();
1783
+ }
1784
+ const dataReady = this.waitForDataSourceLoad(this.vm().dataSource);
1785
+ this.vm().dataSource.refresh({ reset: resetPagination });
1786
+ await dataReady;
1787
+ }
1788
+ /** Marks a card with the same post-create highlight used by the data table. */
1789
+ highlightCardById(rowId) {
1790
+ const key = this.rowKey();
1791
+ const found = this.items().some((row) => String(get(row, key)) === String(rowId));
1792
+ if (found) {
1793
+ this.highlightedRowId.set(rowId);
1794
+ }
1795
+ }
1796
+ clearCardHighlight() {
1797
+ this.highlightedRowId.set(null);
1798
+ }
1799
+ isRowHighlighted(row) {
1800
+ const highlightedId = this.highlightedRowId();
1801
+ if (highlightedId == null) {
1802
+ return false;
1803
+ }
1804
+ return String(get(row, this.rowKey())) === String(highlightedId);
1805
+ }
1806
+ /** Syncs visible items from the shared data source without triggering a load. */
1807
+ syncFromDataSource() {
1808
+ this.syncItemsFromDataSource();
1809
+ }
1810
+ /** Lightweight snapshot for entity list layout diagnostics. */
1811
+ getTraceSnapshot() {
1812
+ return {
1813
+ itemCount: this.items().length,
1814
+ loading: this.loading(),
1815
+ pageIndex: this.pageIndex(),
1816
+ pageSize: this.pageSize(),
1817
+ };
1818
+ }
1819
+ //#endregion
1820
+ //#region ---- Selection ----
1821
+ trackRow(index, row) {
1822
+ if (!isRowRecord(row)) {
1823
+ return `row-${index}`;
1824
+ }
1825
+ const id = get(row, this.rowKey());
1826
+ return id != null ? String(id) : `row-${index}`;
1827
+ }
1828
+ isRowSelected(row) {
1829
+ const key = this.rowKey();
1830
+ const rowId = get(row, key);
1831
+ return this.vm()
1832
+ .selectedItems()
1833
+ .some((item) => getRowId(item, key) === rowId);
1834
+ }
1835
+ onRowSelectionChange(row, selected) {
1836
+ const key = this.rowKey();
1837
+ const rowId = getRowId(row, key);
1838
+ const current = this.vm().selectedItems();
1839
+ if (selected) {
1840
+ if (!current.some((item) => getRowId(item, key) === rowId)) {
1841
+ this.vm().selectedItems.set([...current, row]);
1842
+ }
1843
+ return;
1844
+ }
1845
+ this.vm().selectedItems.set(current.filter((item) => getRowId(item, key) !== rowId));
1846
+ }
1847
+ //#endregion
1848
+ //#region ---- Commands & Paging ----
1849
+ async onCommandClick(name, row) {
1850
+ await this.vm().executeCommand(name, row);
1851
+ }
1852
+ onPageChanged(event) {
1853
+ applyDataSourcePagingWithoutLoad(this.vm().dataSource, { take: event.take, skip: event.skip });
1854
+ this.pageIndex.set(getDataSourcePageIndex(this.vm().dataSource));
1855
+ this.pageSize.set(event.take);
1856
+ if (!event.isUserInteraction) {
1857
+ return;
1858
+ }
1859
+ if (!this.vm().skipListPagingPersistence) {
1860
+ this.vm().saveListPaging(event.take, event.skip);
1861
+ }
1862
+ this.pageChanged.emit(event);
1863
+ void this.reload(false);
1864
+ }
1865
+ //#endregion
1866
+ //#region ---- Private Helpers ----
1867
+ syncItemsFromDataSource() {
1868
+ const ds = this.vm().dataSource;
1869
+ const pageItems = [...ds.items];
1870
+ this.items.set(pageItems.filter(isRowRecord));
1871
+ this.totalCount.set(ds.totalCount ?? this.items().length);
1872
+ this.pageIndex.set(getDataSourcePageIndex(this.vm().dataSource));
1873
+ this.pageSize.set(this.vm().dataSource.config.pageSize ?? 10);
1874
+ }
1875
+ syncPagingFromDataSource(vm) {
1876
+ this.pageIndex.set(getDataSourcePageIndex(vm.dataSource));
1877
+ this.pageSize.set(vm.dataSource.config.pageSize ?? 10);
1878
+ }
1879
+ waitForDataSourceLoad(dataSource) {
1880
+ return new Promise((resolve) => {
1881
+ let settled = false;
1882
+ const finish = () => {
1883
+ if (settled) {
1884
+ return;
1885
+ }
1886
+ settled = true;
1887
+ clearTimeout(timeoutId);
1888
+ loadingSub.unsubscribe();
1889
+ changedSub.unsubscribe();
1890
+ resolve();
1891
+ };
1892
+ const changedSub = dataSource.onChanged.subscribe(() => {
1893
+ if (!dataSource.isLoading) {
1894
+ queueMicrotask(finish);
1895
+ }
1896
+ });
1897
+ const loadingSub = dataSource.onLoadingChanged.subscribe((isLoading) => {
1898
+ if (!isLoading) {
1899
+ queueMicrotask(finish);
1900
+ }
1901
+ });
1902
+ const timeoutId = setTimeout(finish, 10_000);
1903
+ });
1904
+ }
1905
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1906
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterListCardListComponent, isStandalone: true, selector: "axp-entity-master-list-card-list", inputs: { vm: { classPropertyName: "vm", publicName: "vm", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { pageChanged: "pageChanged" }, ngImport: i0, template: "<div class=\"axp-entity-card-list\">\n @if (loading()) {\n <div class=\"axp-entity-card-list__grid\" aria-busy=\"true\" aria-live=\"polite\">\n @for (index of skeletonPlaceholders(); track index) {\n <axp-entity-master-list-card-item-skeleton\n [fieldCount]=\"skeletonFieldCount()\"\n [badgeCount]=\"skeletonBadgeCount()\"\n [showContent]=\"vm().cardContentExpanded()\"\n [showFooter]=\"skeletonShowFooter()\"\n />\n }\n </div>\n } @else if (!items().length) {\n <div\n class=\"axp-entity-card-list__empty ax-flex ax-flex-col ax-items-center ax-justify-center ax-flex-1 ax-p-8 ax-text-muted\"\n >\n <i class=\"fa-light fa-grid-2 ax-text-3xl ax-mb-2\" aria-hidden=\"true\"></i>\n <span>{{ '@general:terms.interface.no-items' | translate | async }}</span>\n </div>\n } @else {\n <div class=\"axp-entity-card-list__grid\">\n @for (row of items(); track trackRow($index, row)) {\n @if (row) {\n <axp-entity-master-list-card-item\n [vm]=\"vm()\"\n [row]=\"row\"\n [showSelection]=\"showSelection()\"\n [selected]=\"isRowSelected(row)\"\n [showNewBadge]=\"isRowHighlighted(row)\"\n (selectionChange)=\"onRowSelectionChange(row, $event)\"\n (commandClick)=\"onCommandClick($event, row)\"\n />\n }\n }\n </div>\n }\n\n <div\n class=\"axp-entity-card-list__pager ax-border-t ax-border-light\"\n [class.axp-entity-card-list__pager--compact]=\"deviceService.isSmall()\"\n >\n <ax-data-pager\n [displayMode]=\"pagerDisplayMode()\"\n [total]=\"totalCount()\"\n [size]=\"pageSize()\"\n [value]=\"pageIndex() + 1\"\n (onChanged)=\"onPageChanged($event)\"\n ></ax-data-pager>\n </div>\n</div>\n", styles: [":host.axp-entity-list-host{display:flex;height:100%;min-height:0px;flex-direction:column;overflow:hidden}.axp-entity-card-list{display:flex;min-height:0px;flex:1 1 0%;flex-direction:column;gap:1rem}.axp-entity-card-list__empty{border-radius:.5rem;border-width:1px;border-style:dashed;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1))}.axp-entity-card-list__grid{display:grid;min-height:0px;flex:1 1 0%;grid-template-columns:repeat(1,minmax(0,1fr));gap:1rem;overflow:auto}@media(min-width:768px){.axp-entity-card-list__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1280px){.axp-entity-card-list__grid{grid-template-columns:repeat(3,minmax(0,1fr))}}.axp-entity-card-list__grid{align-content:start;align-items:start;grid-auto-rows:max-content}.axp-entity-card-list__pager{flex-shrink:0;padding-top:.5rem}.axp-entity-card-list__pager ax-data-pager{width:100%}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager{justify-content:center}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager .ax-compact-prefix{display:flex;width:100%;justify-content:center}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager .ax-compact-suffix{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDataPagerModule }, { kind: "component", type: i1$3.AXDataPagerComponent, selector: "ax-data-pager", inputs: ["value", "name", "disabled", "readonly", "isLoading", "size", "total", "displayMode"], outputs: ["valueChange", "onValueChanged", "disabledChange", "readonlyChange", "displayModeChange", "onChanged"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPEntityMasterListCardItemComponent, selector: "axp-entity-master-list-card-item", inputs: ["vm", "row", "selected", "showNewBadge", "showSelection"], outputs: ["selectionChange", "commandClick"] }, { kind: "component", type: AXPEntityMasterListCardItemSkeletonComponent, selector: "axp-entity-master-list-card-item-skeleton", inputs: ["fieldCount", "badgeCount", "showFooter", "showHeaderMenu", "showContent"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1907
+ }
1908
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListCardListComponent, decorators: [{
1909
+ type: Component,
1910
+ args: [{ selector: 'axp-entity-master-list-card-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1911
+ CommonModule,
1912
+ AXButtonModule,
1913
+ AXDataPagerModule,
1914
+ AXTranslationModule,
1915
+ AXPEntityMasterListCardItemComponent,
1916
+ AXPEntityMasterListCardItemSkeletonComponent,
1917
+ ], template: "<div class=\"axp-entity-card-list\">\n @if (loading()) {\n <div class=\"axp-entity-card-list__grid\" aria-busy=\"true\" aria-live=\"polite\">\n @for (index of skeletonPlaceholders(); track index) {\n <axp-entity-master-list-card-item-skeleton\n [fieldCount]=\"skeletonFieldCount()\"\n [badgeCount]=\"skeletonBadgeCount()\"\n [showContent]=\"vm().cardContentExpanded()\"\n [showFooter]=\"skeletonShowFooter()\"\n />\n }\n </div>\n } @else if (!items().length) {\n <div\n class=\"axp-entity-card-list__empty ax-flex ax-flex-col ax-items-center ax-justify-center ax-flex-1 ax-p-8 ax-text-muted\"\n >\n <i class=\"fa-light fa-grid-2 ax-text-3xl ax-mb-2\" aria-hidden=\"true\"></i>\n <span>{{ '@general:terms.interface.no-items' | translate | async }}</span>\n </div>\n } @else {\n <div class=\"axp-entity-card-list__grid\">\n @for (row of items(); track trackRow($index, row)) {\n @if (row) {\n <axp-entity-master-list-card-item\n [vm]=\"vm()\"\n [row]=\"row\"\n [showSelection]=\"showSelection()\"\n [selected]=\"isRowSelected(row)\"\n [showNewBadge]=\"isRowHighlighted(row)\"\n (selectionChange)=\"onRowSelectionChange(row, $event)\"\n (commandClick)=\"onCommandClick($event, row)\"\n />\n }\n }\n </div>\n }\n\n <div\n class=\"axp-entity-card-list__pager ax-border-t ax-border-light\"\n [class.axp-entity-card-list__pager--compact]=\"deviceService.isSmall()\"\n >\n <ax-data-pager\n [displayMode]=\"pagerDisplayMode()\"\n [total]=\"totalCount()\"\n [size]=\"pageSize()\"\n [value]=\"pageIndex() + 1\"\n (onChanged)=\"onPageChanged($event)\"\n ></ax-data-pager>\n </div>\n</div>\n", styles: [":host.axp-entity-list-host{display:flex;height:100%;min-height:0px;flex-direction:column;overflow:hidden}.axp-entity-card-list{display:flex;min-height:0px;flex:1 1 0%;flex-direction:column;gap:1rem}.axp-entity-card-list__empty{border-radius:.5rem;border-width:1px;border-style:dashed;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1))}.axp-entity-card-list__grid{display:grid;min-height:0px;flex:1 1 0%;grid-template-columns:repeat(1,minmax(0,1fr));gap:1rem;overflow:auto}@media(min-width:768px){.axp-entity-card-list__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1280px){.axp-entity-card-list__grid{grid-template-columns:repeat(3,minmax(0,1fr))}}.axp-entity-card-list__grid{align-content:start;align-items:start;grid-auto-rows:max-content}.axp-entity-card-list__pager{flex-shrink:0;padding-top:.5rem}.axp-entity-card-list__pager ax-data-pager{width:100%}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager{justify-content:center}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager .ax-compact-prefix{display:flex;width:100%;justify-content:center}.axp-entity-card-list__pager--compact ::ng-deep ax-data-pager .ax-compact-suffix{display:none}\n"] }]
1918
+ }], ctorParameters: () => [], propDecorators: { vm: [{ type: i0.Input, args: [{ isSignal: true, alias: "vm", required: true }] }], pageChanged: [{ type: i0.Output, args: ["pageChanged"] }] } });
1919
+ function getRowId(row, key) {
1920
+ if (isRowRecord(row)) {
1921
+ return get(row, key);
1922
+ }
1923
+ return undefined;
1924
+ }
1925
+ function isRowRecord(row) {
1926
+ return row != null && typeof row === 'object' && !Array.isArray(row);
1927
+ }
1928
+
1929
+ class AXPEntityMasterListDataTableComponent {
1930
+ constructor() {
1931
+ //#region ---- Inputs & Outputs ----
1932
+ this.vm = input.required(...(ngDevMode ? [{ debugName: "vm" }] : /* istanbul ignore next */ []));
1933
+ this.pageChanged = output();
1934
+ //#endregion
1935
+ //#region ---- View References ----
1936
+ this.gridRef = viewChild('grid', ...(ngDevMode ? [{ debugName: "gridRef" }] : /* istanbul ignore next */ []));
1937
+ //#endregion
1938
+ //#region ---- Computed ----
1939
+ this.resolveRowCssClass = (row, _rowIndex) => {
1940
+ return this.vm().resolveTableRowCssClass(row);
1941
+ };
1942
+ this.resolveCellCssClass = (row, column, _rowIndex) => {
1943
+ return this.vm().resolveTableCellCssClass(row, this.resolveTableColumnName(column));
1944
+ };
1945
+ this.commandRowItems = computed(() => {
1946
+ return this.vm()
1947
+ .primaryRowActions()
1948
+ .map((c) => ({
1949
+ icon: c.icon,
1950
+ name: c.name,
1951
+ text: translateSync(c.title),
1952
+ color: c.color,
1953
+ disabled: c.disabled,
1954
+ default: c.default,
1955
+ }));
1956
+ }, ...(ngDevMode ? [{ debugName: "commandRowItems" }] : /* istanbul ignore next */ []));
1957
+ this.getDropdownRowItems = (rowData) => {
1958
+ return Promise.resolve(this.dropdownRowItems(rowData));
1959
+ };
1960
+ this.getCommandRowItems = () => {
1961
+ return this.commandRowItems();
1962
+ };
1963
+ }
1964
+ /** Exposed for parent refresh and expanded-row persistence. */
1965
+ grid() {
1966
+ return this.gridRef();
1967
+ }
1968
+ //#endregion
1969
+ //#region ---- Table Event Handlers ----
1970
+ onColumnSizeChanged(e) {
1971
+ if (e.isUserInteraction && e.type === 'end') {
1972
+ this.vm().saveSettings('columnSizes', e.data);
1973
+ }
1974
+ }
1975
+ onPageChanged(e) {
1976
+ if (e.isUserInteraction && !this.vm().skipListPagingPersistence) {
1977
+ this.vm().saveListPaging(e.take, e.skip);
1978
+ }
1979
+ this.pageChanged.emit(e);
1980
+ }
1981
+ onColumnsOrderChanged(e) {
1982
+ if (e.isUserInteraction) {
1983
+ this.vm().onColumnsOrderChanged(e.data.event);
1984
+ }
1985
+ }
1986
+ async handleRowDbClick(e) {
1987
+ if (this.grid()?.dataSource.isLoading) {
1988
+ return;
1989
+ }
1990
+ const allItems = [...this.commandRowItems(), ...(await this.dropdownRowItems(e.data))];
1991
+ const defaultAction = allItems.find((c) => {
1992
+ const commandName = c.name.split('&')[0];
1993
+ return (c.default || commandName === 'open-entity') && !c.disabled;
1994
+ });
1995
+ if (!defaultAction) {
1996
+ return;
1997
+ }
1998
+ const d = {
1999
+ component: e.component,
2000
+ name: defaultAction.name,
2001
+ data: e.data,
2002
+ };
2003
+ await this.handleRowCommandClick(d);
2004
+ }
2005
+ async handleRowCommandClick(e) {
2006
+ if (this.grid()?.dataSource.isLoading) {
2007
+ return;
2008
+ }
2009
+ if (e.setLoading) {
2010
+ e.setLoading(true);
2011
+ }
2012
+ await this.vm().executeCommand(e.name, e.data);
2013
+ if (e.setLoading) {
2014
+ e.setLoading(false);
2015
+ }
2016
+ }
2017
+ handleSelectedRowsChange(rows) {
2018
+ this.vm().selectedItems.set(rows);
2019
+ }
2020
+ handleColumnSort(columnName, event) {
2021
+ void this.vm().toggleColumnSort(columnName, event.ctrlKey || event.metaKey);
2022
+ }
2023
+ //#endregion
2024
+ //#region ---- Sort Helpers ----
2025
+ resolveColumnSortDirection(sortedFields, columnName) {
2026
+ const dir = sortedFields.find((s) => s.name === columnName)?.dir;
2027
+ return dir === 'asc' || dir === 'desc' ? dir : undefined;
2028
+ }
2029
+ resolveColumnSortIndex(sortedFields, columnName) {
2030
+ const index = sortedFields.findIndex((s) => s.name === columnName);
2031
+ return index >= 0 ? index + 1 : undefined;
2032
+ }
2033
+ //#endregion
2034
+ //#region ---- Private Helpers ----
2035
+ resolveTableColumnName(column) {
2036
+ const prefix = 'col-';
2037
+ return column.name.replace(prefix, '');
2038
+ }
2039
+ async dropdownRowItems(rowData) {
2040
+ return (await this.vm().secondaryRowActions(rowData)).map((c) => ({
2041
+ icon: c.icon,
2042
+ name: c.name,
2043
+ text: c.title,
2044
+ color: c.color,
2045
+ disabled: c.disabled,
2046
+ default: c.default,
2047
+ divided: c.separated,
2048
+ }));
2049
+ }
2050
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListDataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2051
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterListDataTableComponent, isStandalone: true, selector: "axp-entity-master-list-data-table", inputs: { vm: { classPropertyName: "vm", publicName: "vm", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { pageChanged: "pageChanged" }, host: { styleAttribute: "height: -webkit-fill-available;" }, viewQueries: [{ propertyName: "gridRef", first: true, predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-data-table\n id=\"axp-entity-table\"\n [allowReordering]=\"true\"\n (onColumnsOrderChanged)=\"onColumnsOrderChanged($event)\"\n #grid\n [showFooter]=\"false\"\n class=\"ax-flex-1\"\n [paging]=\"true\"\n [fetchDataMode]=\"'manual'\"\n [parentField]=\"vm().parentKey()\"\n [loading]=\"{ enabled: true, animation: true }\"\n [dataSource]=\"vm().dataSource\"\n [highlightText]=\"vm().inlineFilterHighlightText()\"\n [rowCssClass]=\"resolveRowCssClass\"\n [cellCssClass]=\"resolveCellCssClass\"\n (selectedRowsChange)=\"handleSelectedRowsChange($event)\"\n (onRowDbClick)=\"handleRowDbClick($event)\"\n (onColumnSizeChanged)=\"onColumnSizeChanged($event)\"\n (onPageChanged)=\"onPageChanged($event)\"\n>\n @if (vm().showIndexColumn()) {\n <ax-index-column\n id=\"axp-table-col-index\"\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm().selectedScopeActionsCount()) {\n <ax-select-column id=\"axp-table-col-select\" fixed=\"start\" [width]=\"'60px'\"></ax-select-column>\n }\n @for (col of vm().columns(); track col.name) {\n @if (col.visible) {\n <axp-widget-column-renderer\n [attr.id]=\"'axp-table-col-' + col.name\"\n [expandHandler]=\"$index === 0 && vm().parentKey() ? true : false\"\n [caption]=\"(col.title | translate | async)!\"\n [node]=\"col.node()\"\n [customWidth]=\"col.width\"\n [sortEnabled]=\"!!col.sortEnabled\"\n [headerSortDirection]=\"resolveColumnSortDirection(vm().sortedFields(), col.name)\"\n [headerSortPriority]=\"resolveColumnSortIndex(vm().sortedFields(), col.name)\"\n (sortToggle)=\"handleColumnSort(col.name, $event)\"\n ></axp-widget-column-renderer>\n }\n }\n @if (getCommandRowItems().length) {\n <ax-command-column\n id=\"axp-table-col-commands\"\n fixed=\"end\"\n [width]=\"getCommandRowItems().length * 70 + 'px'\"\n [items]=\"getCommandRowItems()\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-command-column>\n }\n <ax-dropdown-command-column\n id=\"axp-table-col-dropdown-commands\"\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"getDropdownRowItems\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-dropdown-command-column>\n</ax-data-table>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i7.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i7.AXRowIndexColumnComponent, selector: "ax-index-column", inputs: ["width", "caption", "fixed", "footerTemplate", "padZero"] }, { kind: "component", type: i7.AXRowSelectColumnComponent, selector: "ax-select-column", inputs: ["width", "caption", "fixed"] }, { kind: "component", type: i7.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "component", type: i7.AXRowDropdownCommandColumnComponent, selector: "ax-dropdown-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "emptyStateTemplate", "emptyStateText", "items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i7$1.AXPWidgetColumnRendererComponent, selector: "axp-widget-column-renderer", inputs: ["caption", "customExpandIcon", "customCollapseIcon", "customWidth", "node", "footerTemplate", "expandHandler", "sortEnabled", "headerSortDirection", "headerSortPriority", "columnFixed", "cellTemplate", "headerTemplate"], outputs: ["sortToggle"] }, { kind: "ngmodule", type: AXPWidgetsModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2052
+ }
2053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListDataTableComponent, decorators: [{
2054
+ type: Component,
2055
+ args: [{ selector: 'axp-entity-master-list-data-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXDataTableModule, AXTranslationModule, AXPWidgetCoreModule, AXPWidgetsModule], host: {
2056
+ style: 'height: -webkit-fill-available;',
2057
+ }, template: "<ax-data-table\n id=\"axp-entity-table\"\n [allowReordering]=\"true\"\n (onColumnsOrderChanged)=\"onColumnsOrderChanged($event)\"\n #grid\n [showFooter]=\"false\"\n class=\"ax-flex-1\"\n [paging]=\"true\"\n [fetchDataMode]=\"'manual'\"\n [parentField]=\"vm().parentKey()\"\n [loading]=\"{ enabled: true, animation: true }\"\n [dataSource]=\"vm().dataSource\"\n [highlightText]=\"vm().inlineFilterHighlightText()\"\n [rowCssClass]=\"resolveRowCssClass\"\n [cellCssClass]=\"resolveCellCssClass\"\n (selectedRowsChange)=\"handleSelectedRowsChange($event)\"\n (onRowDbClick)=\"handleRowDbClick($event)\"\n (onColumnSizeChanged)=\"onColumnSizeChanged($event)\"\n (onPageChanged)=\"onPageChanged($event)\"\n>\n @if (vm().showIndexColumn()) {\n <ax-index-column\n id=\"axp-table-col-index\"\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm().selectedScopeActionsCount()) {\n <ax-select-column id=\"axp-table-col-select\" fixed=\"start\" [width]=\"'60px'\"></ax-select-column>\n }\n @for (col of vm().columns(); track col.name) {\n @if (col.visible) {\n <axp-widget-column-renderer\n [attr.id]=\"'axp-table-col-' + col.name\"\n [expandHandler]=\"$index === 0 && vm().parentKey() ? true : false\"\n [caption]=\"(col.title | translate | async)!\"\n [node]=\"col.node()\"\n [customWidth]=\"col.width\"\n [sortEnabled]=\"!!col.sortEnabled\"\n [headerSortDirection]=\"resolveColumnSortDirection(vm().sortedFields(), col.name)\"\n [headerSortPriority]=\"resolveColumnSortIndex(vm().sortedFields(), col.name)\"\n (sortToggle)=\"handleColumnSort(col.name, $event)\"\n ></axp-widget-column-renderer>\n }\n }\n @if (getCommandRowItems().length) {\n <ax-command-column\n id=\"axp-table-col-commands\"\n fixed=\"end\"\n [width]=\"getCommandRowItems().length * 70 + 'px'\"\n [items]=\"getCommandRowItems()\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-command-column>\n }\n <ax-dropdown-command-column\n id=\"axp-table-col-dropdown-commands\"\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"getDropdownRowItems\"\n (onItemClick)=\"handleRowCommandClick($event)\"\n ></ax-dropdown-command-column>\n</ax-data-table>\n" }]
2058
+ }], propDecorators: { vm: [{ type: i0.Input, args: [{ isSignal: true, alias: "vm", required: true }] }], pageChanged: [{ type: i0.Output, args: ["pageChanged"] }], gridRef: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
2059
+
2060
+ class AXPEntityMasterListViewComponent extends AXPPageLayoutBaseComponent {
2061
+ constructor(platform) {
2062
+ super();
2063
+ this.platform = platform;
2064
+ //#region ---- Services & Dependencies ----
2065
+ this.activeRoute = inject(ActivatedRoute);
2066
+ //#endregion
2067
+ //#region ---- View State ----
2068
+ this.vm = this.activeRoute.snapshot.data['vm'];
2069
+ this.store = inject(AXPLayoutThemeService);
2070
+ this.searchBarShown = signal(true, ...(ngDevMode ? [{ debugName: "searchBarShown" }] : /* istanbul ignore next */ []));
2071
+ this.activeEndSideView = signal('column', ...(ngDevMode ? [{ debugName: "activeEndSideView" }] : /* istanbul ignore next */ []));
2072
+ this.categorySearchValue = signal('', ...(ngDevMode ? [{ debugName: "categorySearchValue" }] : /* istanbul ignore next */ []));
2073
+ //#endregion
2074
+ //#region ---- View References ----
2075
+ this.dataTable = viewChild(AXPEntityMasterListDataTableComponent, ...(ngDevMode ? [{ debugName: "dataTable" }] : /* istanbul ignore next */ []));
2076
+ this.cardList = viewChild(AXPEntityMasterListCardListComponent, ...(ngDevMode ? [{ debugName: "cardList" }] : /* istanbul ignore next */ []));
2077
+ this.listHostReady = signal(false, ...(ngDevMode ? [{ debugName: "listHostReady" }] : /* istanbul ignore next */ []));
2078
+ this.previousAppTabActive = false;
2079
+ this.lastEvaluatedViewName = null;
2080
+ this.componentDestroyed = new Subject();
2081
+ this.isRestoringExpandedRows = false;
2082
+ this.restoreExpandedRowsScheduled = false;
2083
+ /** Blocks deferred `replaceUrl` view sync after destroy or once details navigation has started. */
2084
+ this.listQueryParamSyncDisabled = false;
2085
+ effect(() => {
2086
+ const grid = this.grid();
2087
+ if (grid) {
2088
+ grid.selectedRows = this.vm.selectedItems();
2089
+ }
2090
+ });
2091
+ this.vm.events$.subscribe(async (e) => {
2092
+ const refreshTargetId = e.meta?.refreshTargetId;
2093
+ const highlightRowId = e.meta?.highlightRowId;
2094
+ const resetPagination = e.meta?.resetPagination === true || (highlightRowId != null && refreshTargetId == null);
2095
+ if (e.action == 'refresh') {
2096
+ traceEntityMasterList('list-view', 'refresh event received', () => ({
2097
+ viewName: this.vm.view().name,
2098
+ activeListLayout: this.vm.activeListLayout(),
2099
+ resetPagination,
2100
+ refreshTargetId,
2101
+ highlightRowId,
2102
+ hasGrid: !!this.grid(),
2103
+ hasCardList: !!this.cardList(),
2104
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
2105
+ }));
2106
+ if (highlightRowId == null) {
2107
+ this.cardList()?.clearCardHighlight();
2108
+ }
2109
+ if (resetPagination) {
2110
+ this.vm.resetListPagingSkip();
2111
+ }
2112
+ if (refreshTargetId) {
2113
+ await this.grid()?.refreshItemChildren(refreshTargetId);
2114
+ this.updateParentHasChildAfterRefresh(refreshTargetId);
2115
+ }
2116
+ else {
2117
+ await this.reloadListData(resetPagination);
2118
+ }
2119
+ if (highlightRowId != null) {
2120
+ await this.highlightRowById(highlightRowId);
2121
+ }
2122
+ traceEntityMasterList('list-view', 'refresh event handled', () => this.buildListHostTraceSnapshot());
2123
+ }
2124
+ });
2125
+ effect(() => {
2126
+ if (!this.listHostReady()) {
2127
+ return;
2128
+ }
2129
+ const currentViewName = this.vm.view().name;
2130
+ const urlView = this.activeRoute.snapshot.queryParamMap.get('view');
2131
+ if (currentViewName && currentViewName !== urlView) {
2132
+ this.syncListViewQueryParam(currentViewName);
2133
+ }
2134
+ });
2135
+ effect(() => {
2136
+ const currentViewName = this.vm.view().name;
2137
+ if (!this.listHostReady()) {
2138
+ this.lastEvaluatedViewName = currentViewName;
2139
+ return;
2140
+ }
2141
+ if (this.lastEvaluatedViewName !== currentViewName) {
2142
+ traceEntityMasterList('list-view', 'view name changed in list host', () => ({
2143
+ previousViewName: this.lastEvaluatedViewName,
2144
+ nextViewName: currentViewName,
2145
+ activeListLayout: this.vm.activeListLayout(),
2146
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
2147
+ }));
2148
+ this.lastEvaluatedViewName = currentViewName;
2149
+ this.recompute();
2150
+ this.scheduleRestoreExpandedRows();
2151
+ }
2152
+ });
2153
+ effect(() => {
2154
+ const layout = this.vm.activeListLayout();
2155
+ if (!this.listHostReady()) {
2156
+ return;
2157
+ }
2158
+ const previous = this.previousActiveListLayout;
2159
+ this.previousActiveListLayout = layout;
2160
+ if (previous === undefined || previous === layout) {
2161
+ return;
2162
+ }
2163
+ traceEntityMasterList('list-view', 'list layout switched', () => ({
2164
+ previousLayout: previous,
2165
+ nextLayout: layout,
2166
+ viewName: this.vm.view().name,
2167
+ hasGrid: !!this.grid(),
2168
+ hasCardList: !!this.cardList(),
2169
+ ...this.buildListHostTraceSnapshot(),
2170
+ }));
2171
+ if (layout === 'table') {
2172
+ // TODO: Avoid redundant list query when switching card → table if the shared data source
2173
+ // already has the current page. grid.refresh() refetches in manual mode even though rows
2174
+ // are cached; need a cache-only repaint API on ax-data-table (see entity-master-list traces).
2175
+ void this.reloadListData(false);
2176
+ return;
2177
+ }
2178
+ this.cardList()?.syncFromDataSource();
2179
+ });
2180
+ effect(() => {
2181
+ if (!this.listHostReady()) {
2182
+ return;
2183
+ }
2184
+ const pageTabKey = this.getPageTabKey();
2185
+ const activeTabKey = this.tabNavigation.activeKey();
2186
+ const isThisTabActive = !!pageTabKey && activeTabKey === pageTabKey;
2187
+ const wasActive = this.previousAppTabActive;
2188
+ this.previousAppTabActive = isThisTabActive;
2189
+ if (!isThisTabActive || wasActive) {
2190
+ return;
2191
+ }
2192
+ traceEntityMasterList('list-view', 'app tab activated for entity list', () => ({
2193
+ pageTabKey,
2194
+ activeTabKey,
2195
+ viewName: this.vm.view().name,
2196
+ activeListLayout: this.vm.activeListLayout(),
2197
+ ...this.buildListHostTraceSnapshot(),
2198
+ }));
2199
+ });
2200
+ }
2201
+ async ngOnInit() {
2202
+ this.router.events
2203
+ .pipe(filter((event) => event instanceof NavigationStart), takeUntil(this.componentDestroyed))
2204
+ .subscribe((event) => {
2205
+ if (axpIsEntityListRoute(this.router) && !axpIsEntityListPath(event.url)) {
2206
+ this.listQueryParamSyncDisabled = true;
2207
+ }
2208
+ });
2209
+ this.queryParamSub = this.activeRoute.queryParamMap.subscribe(async (qp) => {
2210
+ const viewFromUrl = qp.get('view');
2211
+ traceEntityMasterList('list-view', 'route view query param changed', () => ({
2212
+ viewFromUrl,
2213
+ currentViewName: this.vm.view().name,
2214
+ activeListLayout: this.vm.activeListLayout(),
2215
+ }));
2216
+ // TODO: Consolidate list init setView calls (resolver, toolbar ngOnInit, queryParamSub, ngAfterViewInit).
2217
+ await this.vm.setView(viewFromUrl);
2218
+ this.scheduleRestoreExpandedRows();
2219
+ });
2220
+ }
2221
+ async ngAfterViewInit() {
2222
+ const viewFromUrl = this.activeRoute.snapshot.queryParamMap.get('view');
2223
+ // TODO: See queryParamSub — setView is also invoked from resolver and toolbar on first load.
2224
+ await this.vm.setView(viewFromUrl);
2225
+ const resolvedView = this.vm.view().name;
2226
+ const currentUrlView = this.activeRoute.snapshot.queryParamMap.get('view');
2227
+ if (currentUrlView !== resolvedView) {
2228
+ this.syncListViewQueryParam(resolvedView);
2229
+ }
2230
+ this.listHostReady.set(true);
2231
+ this.bindExpandedRowPersistence();
2232
+ this.scheduleRestoreExpandedRows();
2233
+ this.recompute();
2234
+ }
2235
+ ngOnDestroy() {
2236
+ this.listQueryParamSyncDisabled = true;
2237
+ this.componentDestroyed.next();
2238
+ this.componentDestroyed.complete();
2239
+ this.queryParamSub?.unsubscribe();
2240
+ this.vm.destroy();
2241
+ }
2242
+ //#endregion
2243
+ //#region ---- List Layout Handlers ----
2244
+ onListPageChanged() {
2245
+ this.scheduleRestoreExpandedRows();
2246
+ }
2247
+ handleUnselectAll() {
2248
+ this.vm.selectedItems.set([]);
2249
+ }
2250
+ //#endregion
2251
+ //#region ---- Toolbar & Sidebar Handlers ----
2252
+ makeResponsive(value) {
2253
+ if (this.platform.is('Mobile') || this.platform.is('SM')) {
2254
+ return '';
2255
+ }
2256
+ return value;
2257
+ }
2258
+ toggleSideBar(sideBar) {
2259
+ this.toggleEndSide();
2260
+ if (sideBar) {
2261
+ this.activeEndSideView.set(sideBar);
2262
+ }
2263
+ }
2264
+ toggleSearchBar() {
2265
+ this.searchBarShown.update((v) => !v);
2266
+ }
2267
+ handleResetClick(sideBar) {
2268
+ switch (sideBar) {
2269
+ case 'filter': {
2270
+ this.vm.resetFilters();
2271
+ break;
2272
+ }
2273
+ case 'sort': {
2274
+ this.vm.resetSorts();
2275
+ break;
2276
+ }
2277
+ case 'column': {
2278
+ this.vm.resetColumns();
2279
+ break;
2280
+ }
2281
+ }
2282
+ }
2283
+ handleApplyClick(sideBar) {
2284
+ this.vm.applyFilterAndSort();
2285
+ this.toggleEndSide();
2286
+ }
2287
+ toggleCategoryDrawer() {
2288
+ this.toggleStartSide();
2289
+ }
2290
+ //#endregion
2291
+ //#region ---- Page Layout ----
2292
+ getPageTitle() {
2293
+ return this.vm.title();
2294
+ }
2295
+ async getPageDescription() {
2296
+ const description = this.vm.description();
2297
+ if (description) {
2298
+ return this.translateService.translateAsync(description);
2299
+ }
2300
+ return '';
2301
+ }
2302
+ getPageBreadcrumbs() {
2303
+ return this.vm.beardcrumbs();
2304
+ }
2305
+ async getPrimaryMenuItems() {
2306
+ return (await this.vm.getPrimaryActions())
2307
+ .filter((tr) => !tr.isChild)
2308
+ .map((tr) => ({
2309
+ name: tr.name,
2310
+ title: tr.title,
2311
+ icon: tr.icon,
2312
+ color: tr.color,
2313
+ disabled: tr.disabled,
2314
+ items: tr.items?.map((sub) => ({
2315
+ name: sub.name,
2316
+ title: sub.title,
2317
+ icon: sub.icon,
2318
+ color: sub.color,
2319
+ disabled: sub.disabled,
2320
+ command: {
2321
+ name: sub.name,
2322
+ options: sub.options,
2323
+ metadata: sub.metadata,
2324
+ },
2325
+ })),
2326
+ command: {
2327
+ name: tr.name,
2328
+ options: tr.options,
2329
+ metadata: tr.metadata,
2330
+ },
2331
+ }));
2332
+ }
2333
+ async getSecondaryMenuItems() {
2334
+ return (await this.vm.getSecondaryActions()).map((tr) => ({
2335
+ name: tr.name,
2336
+ title: tr.title,
2337
+ icon: tr.icon,
2338
+ color: tr.color,
2339
+ disabled: tr.disabled,
2340
+ separated: tr.separated,
2341
+ command: {
2342
+ name: tr.name,
2343
+ options: tr.options,
2344
+ metadata: tr.metadata,
2345
+ },
2346
+ }));
2347
+ }
2348
+ async execute(command) {
2349
+ if (command) {
2350
+ this.vm.execute(command);
2351
+ }
2352
+ }
2353
+ //#endregion
2354
+ //#region ---- Grid Helpers ----
2355
+ /**
2356
+ * Syncs the list `view` query param without polluting history.
2357
+ * Re-checks the live URL so a deferred call cannot `replaceUrl` over a details entry.
2358
+ */
2359
+ syncListViewQueryParam(viewName) {
2360
+ if (this.listQueryParamSyncDisabled) {
2361
+ return;
2362
+ }
2363
+ queueMicrotask(() => {
2364
+ if (this.listQueryParamSyncDisabled) {
2365
+ return;
2366
+ }
2367
+ axpReplaceEntityListRouteQueryParams(this.router, { view: viewName });
2368
+ });
2369
+ }
2370
+ async reloadListData(resetPagination = false) {
2371
+ traceEntityMasterList('list-view', 'reloadListData start', () => ({
2372
+ resetPagination,
2373
+ activeListLayout: this.vm.activeListLayout(),
2374
+ viewName: this.vm.view().name,
2375
+ hasGrid: !!this.grid(),
2376
+ hasCardList: !!this.cardList(),
2377
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
2378
+ }));
2379
+ if (!resetPagination) {
2380
+ await this.vm.ensureListPagingResolved();
2381
+ this.vm.applyPagingToDataSourceWithoutLoad();
2382
+ }
2383
+ if (this.vm.activeListLayout() === 'card') {
2384
+ await this.cardList()?.reload(resetPagination);
2385
+ traceEntityMasterList('list-view', 'reloadListData complete (card)', () => this.buildListHostTraceSnapshot());
2386
+ return;
2387
+ }
2388
+ const grid = this.grid();
2389
+ if (grid) {
2390
+ const dataReady = this.waitForDataSourceLoad(grid.dataSource);
2391
+ grid.refresh({ reset: resetPagination });
2392
+ await dataReady;
2393
+ if (!resetPagination) {
2394
+ this.scheduleSyncGridPagerUi();
2395
+ }
2396
+ this.scheduleRestoreExpandedRows();
2397
+ }
2398
+ else {
2399
+ const dataReady = this.waitForDataSourceLoad(this.vm.dataSource);
2400
+ this.vm.dataSource.refresh({ reset: resetPagination });
2401
+ await dataReady;
2402
+ }
2403
+ traceEntityMasterList('list-view', 'reloadListData complete (table)', () => this.buildListHostTraceSnapshot());
2404
+ }
2405
+ buildListHostTraceSnapshot() {
2406
+ const gridRef = this.grid();
2407
+ const gridWithDisplayedRows = gridRef;
2408
+ const displayedRows = gridWithDisplayedRows?.displayedRows?.() ?? [];
2409
+ return {
2410
+ viewName: this.vm.view().name,
2411
+ activeListLayout: this.vm.activeListLayout(),
2412
+ cardContentExpanded: this.vm.cardContentExpanded(),
2413
+ hasGrid: !!gridRef,
2414
+ displayedRowCount: displayedRows.length,
2415
+ cardList: this.cardList()?.getTraceSnapshot() ?? null,
2416
+ dataSource: buildDataSourceTraceSnapshot(this.vm.dataSource),
2417
+ };
2418
+ }
2419
+ /**
2420
+ * Waits until a `refresh()` load cycle completes (loading started, then finished with data).
2421
+ */
2422
+ waitForDataSourceLoad(dataSource) {
2423
+ return new Promise((resolve) => {
2424
+ let settled = false;
2425
+ const finish = () => {
2426
+ if (settled) {
2427
+ return;
2428
+ }
2429
+ settled = true;
2430
+ clearTimeout(timeoutId);
2431
+ loadingSub.unsubscribe();
2432
+ changedSub.unsubscribe();
2433
+ resolve();
2434
+ };
2435
+ const changedSub = dataSource.onChanged.subscribe(() => {
2436
+ if (!dataSource.isLoading) {
2437
+ queueMicrotask(finish);
2438
+ }
2439
+ });
2440
+ const loadingSub = dataSource.onLoadingChanged.subscribe((isLoading) => {
2441
+ if (!isLoading) {
2442
+ queueMicrotask(finish);
2443
+ }
2444
+ });
2445
+ const timeoutId = setTimeout(finish, 10_000);
2446
+ });
2447
+ }
2448
+ grid() {
2449
+ return this.dataTable()?.grid();
2450
+ }
2451
+ /**
2452
+ * Highlights a freshly created row in the active list layout (table or card).
2453
+ */
2454
+ async highlightRowById(rowId) {
2455
+ if (this.vm.activeListLayout() === 'card') {
2456
+ this.cardList()?.highlightCardById(rowId);
2457
+ return;
2458
+ }
2459
+ const gridRef = this.grid();
2460
+ if (!gridRef) {
2461
+ return;
2462
+ }
2463
+ const rowKey = this.vm.dataSource.config?.key ?? 'id';
2464
+ const gridWithDisplayedRows = gridRef;
2465
+ const displayedRows = gridWithDisplayedRows.displayedRows?.() ?? [];
2466
+ const items = displayedRows.length > 0 ? displayedRows : (gridRef.dataSource.cachedItems ?? []);
2467
+ const row = this.findItemById(items, String(rowId), rowKey);
2468
+ if (row) {
2469
+ gridRef.highlightRow(row);
2470
+ }
2471
+ }
2472
+ updateParentHasChildAfterRefresh(parentId) {
2473
+ const gridRef = this.grid();
2474
+ const ds = gridRef?.dataSource;
2475
+ if (!ds?.cachedItems?.length) {
2476
+ return;
2477
+ }
2478
+ const key = ds.config?.key ?? 'id';
2479
+ const parent = this.findItemById(ds.cachedItems, String(parentId), key);
2480
+ if (parent && typeof parent === 'object') {
2481
+ parent['hasChild'] = true;
2482
+ }
2483
+ }
2484
+ findItemById(items, id, key) {
2485
+ for (const item of items) {
2486
+ if (item && typeof item === 'object' && String(item[key]) === id) {
2487
+ return item;
2488
+ }
2489
+ const rec = item;
2490
+ const metaChildren = rec?.['__meta__']?.['children'];
2491
+ const directChildren = rec?.['children'];
2492
+ const childArr = Array.isArray(metaChildren)
2493
+ ? metaChildren
2494
+ : Array.isArray(directChildren)
2495
+ ? directChildren
2496
+ : null;
2497
+ if (childArr?.length) {
2498
+ const found = this.findItemById(childArr, id, key);
2499
+ if (found)
2500
+ return found;
2501
+ }
2502
+ }
2503
+ return null;
2504
+ }
2505
+ bindExpandedRowPersistence() {
2506
+ if (!this.vm.parentKey()) {
2507
+ return;
2508
+ }
2509
+ this.vm.dataSource.onItemExpanded.pipe(takeUntil(this.componentDestroyed)).subscribe((event) => {
2510
+ const expandedItem = event?.expandedItem;
2511
+ if (expandedItem && typeof expandedItem === 'object') {
2512
+ this.vm.handleRowExpandChange(expandedItem);
2513
+ }
2514
+ });
2515
+ }
2516
+ scheduleSyncGridPagerUi() {
2517
+ queueMicrotask(() => {
2518
+ this.syncGridPagerUi();
2519
+ });
2520
+ }
2521
+ syncGridPagerUi() {
2522
+ const gridRef = this.grid();
2523
+ if (!gridRef) {
2524
+ return;
2525
+ }
2526
+ const pageIndex = getDataSourcePageIndex(this.vm.dataSource);
2527
+ const take = this.vm.dataSource.config.pageSize;
2528
+ const pagerPage = pageIndex + 1;
2529
+ const grid = gridRef;
2530
+ this.vm.skipListPagingPersistence = true;
2531
+ try {
2532
+ if (grid.page() !== pageIndex) {
2533
+ grid.page.set(pageIndex);
2534
+ }
2535
+ grid.pageSize.set(take);
2536
+ const pager = grid.dataPager;
2537
+ if (pager && pager.value !== pagerPage) {
2538
+ pager.goToPage(pagerPage);
2539
+ }
2540
+ }
2541
+ finally {
2542
+ this.vm.skipListPagingPersistence = false;
2543
+ }
2544
+ }
2545
+ scheduleRestoreExpandedRows() {
2546
+ if (!this.vm.parentKey() || this.restoreExpandedRowsScheduled) {
2547
+ return;
2548
+ }
2549
+ this.restoreExpandedRowsScheduled = true;
2550
+ queueMicrotask(() => {
2551
+ this.restoreExpandedRowsScheduled = false;
2552
+ void this.restoreExpandedRows();
2553
+ });
2554
+ }
2555
+ async restoreExpandedRows() {
2556
+ if (!this.vm.parentKey() || this.isRestoringExpandedRows) {
2557
+ return;
2558
+ }
2559
+ const expandedRowIds = this.vm.getExpandedRowIds();
2560
+ if (!expandedRowIds.length) {
2561
+ return;
2562
+ }
2563
+ const gridRef = this.grid();
2564
+ if (!gridRef || gridRef.dataSource.isLoading) {
2565
+ return;
2566
+ }
2567
+ const gridWithDisplayedRows = gridRef;
2568
+ const displayedRows = gridWithDisplayedRows.displayedRows?.() ?? [];
2569
+ if (!displayedRows.length) {
2570
+ return;
2571
+ }
2572
+ this.isRestoringExpandedRows = true;
2573
+ this.vm.skipExpandedRowPersistence = true;
2574
+ try {
2575
+ const rowKey = this.vm.dataSource.config?.key ?? 'id';
2576
+ await restoreEntityListExpandedRows({
2577
+ expandedRowIds,
2578
+ rowKey,
2579
+ getDisplayedRows: () => gridWithDisplayedRows.displayedRows?.() ?? [],
2580
+ expandRow: (row) => gridRef.expandRow(row),
2581
+ });
2582
+ }
2583
+ finally {
2584
+ this.vm.skipExpandedRowPersistence = false;
2585
+ this.isRestoringExpandedRows = false;
2586
+ }
2587
+ }
2588
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListViewComponent, deps: [{ token: i1$4.AXPlatform }], target: i0.ɵɵFactoryTarget.Component }); }
2589
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityMasterListViewComponent, isStandalone: true, selector: "axp-entity-master-list", providers: [
2590
+ {
2591
+ provide: AXPPageLayoutBase,
2592
+ useExisting: AXPEntityMasterListViewComponent,
2593
+ },
2594
+ ], viewQueries: [{ propertyName: "dataTable", first: true, predicate: AXPEntityMasterListDataTableComponent, descendants: true, isSignal: true }, { propertyName: "cardList", first: true, predicate: AXPEntityMasterListCardListComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n @if (vm.entityDef.category) {\n <axp-layout-start-side id=\"axp-entity-category-drawer\">\n <axp-entity-category\n id=\"axp-entity-category\"\n class=\"ax-w-80\"\n [vm]=\"vm\"\n [searchValue]=\"categorySearchValue()\"\n ></axp-entity-category>\n </axp-layout-start-side>\n }\n <axp-page-toolbar id=\"axp-entity-toolbar\">\n <axp-entity-master-toolbar-view [viewModel]=\"vm\"></axp-entity-master-toolbar-view>\n </axp-page-toolbar>\n <axp-page-content class=\"ax-overflow-auto ax-pt-0\">\n <div\n class=\"ax-flex ax-items-center ax-justify-between ax-gap-1 ax-pb-1 ax-text-muted ax-text-sm\"\n [class.ax-invisible]=\"!vm.selectedItems().length\"\n >\n <span\n >{{ vm.selectedItems().length }}\n <span>{{ '@general:terms.interface.items-selected' | translate | async }}</span>\n </span>\n <ax-button text=\"@general:terms.interface.unselect-all\" class=\"ax-xs\" (onClick)=\"handleUnselectAll()\"></ax-button>\n </div>\n @if (vm.enabledListLayouts().includes('table')) {\n <axp-entity-master-list-data-table\n class=\"axp-entity-list-host\"\n [class.axp-entity-list-host--hidden]=\"vm.activeListLayout() !== 'table'\"\n [vm]=\"vm\"\n (pageChanged)=\"onListPageChanged()\"\n ></axp-entity-master-list-data-table>\n }\n @if (vm.enabledListLayouts().includes('card')) {\n <axp-entity-master-list-card-list\n class=\"axp-entity-list-host\"\n [class.axp-entity-list-host--hidden]=\"vm.activeListLayout() !== 'card'\"\n [vm]=\"vm\"\n (pageChanged)=\"onListPageChanged()\"\n ></axp-entity-master-list-card-list>\n }\n </axp-page-content>\n</axp-page-layout>\n", styles: ["axp-entity-master-list axp-layout-start-side{min-width:20rem!important;border-inline-end-width:1px}axp-entity-master-list axp-layout-header{padding-bottom:.25rem!important}axp-entity-master-list .axp-entity-list-host{display:flex;height:100%;min-height:0px;flex-direction:column}axp-entity-master-list axp-page-content{display:flex;min-height:0px;flex-direction:column}axp-entity-master-list .axp-entity-list-host--hidden{display:none!important}.cdk-drag-preview{border-radius:.375rem;border-width:1px;--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background:rgba(var(--ax-color-on-surface));padding:.5rem;height:max-content!important}.collapsed-search-box{margin-top:0;height:0px;opacity:0}.view-drawer{width:85vw}@media(min-width:768px){.view-drawer{width:45vw}}@media(min-width:1024px){.view-drawer{width:35vw}}@media(min-width:1536px){.view-drawer{width:20vw}}.view-drawer{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));border-inline-start-width:1px;border-inline-start-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));border-top-width:1px;--tw-border-opacity: 1;border-top-color:rgba(var(--ax-sys-color-primary-600),var(--tw-border-opacity, 1))}.view-drawer ax-header{display:flex;align-items:center;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.5rem 1rem}.view-drawer ax-header h2{font-size:1.25rem;line-height:1.75rem;font-weight:700;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lighter-surface),var(--tw-text-opacity, 1))}.view-drawer ax-footer{position:absolute!important;bottom:0!important;width:100%!important;justify-content:flex-start!important;border-top-width:1px!important;--tw-border-opacity: 1 !important;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1))!important;padding:.5rem 1rem!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "ngmodule", type: AXActionSheetModule }, { kind: "ngmodule", type: AXDrawerModule }, { kind: "ngmodule", type: AXDialogModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "ngmodule", type: AXBreadcrumbsModule }, { kind: "ngmodule", type: AXDropdownButtonModule }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i3$1.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: DragDropModule }, { kind: "ngmodule", type: AXPAuthModule }, { kind: "component", type: AXPEntityMasterToolbarViewComponent, selector: "axp-entity-master-toolbar-view", inputs: ["viewModel"] }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXPThemeLayoutStartSideComponent, selector: "axp-layout-page-start-side, axp-layout-start-side" }, { kind: "component", type: AXPEntityCategoryComponent, selector: "axp-entity-category", inputs: ["vm", "searchValue", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "searchWithChildren"] }, { kind: "component", type: AXPEntityMasterListDataTableComponent, selector: "axp-entity-master-list-data-table", inputs: ["vm"], outputs: ["pageChanged"] }, { kind: "component", type: AXPEntityMasterListCardListComponent, selector: "axp-entity-master-list-card-list", inputs: ["vm"], outputs: ["pageChanged"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2595
+ }
2596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityMasterListViewComponent, decorators: [{
2597
+ type: Component,
2598
+ args: [{ selector: 'axp-entity-master-list', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2599
+ CommonModule,
2600
+ FormsModule,
2601
+ RouterModule,
2602
+ AXButtonModule,
2603
+ AXDecoratorModule,
2604
+ AXBadgeModule,
2605
+ AXDropdownModule,
2606
+ AXPopoverModule,
2607
+ AXFormModule,
2608
+ AXActionSheetModule,
2609
+ AXDrawerModule,
2610
+ AXDialogModule,
2611
+ AXLoadingModule,
2612
+ AXTabsModule,
2613
+ AXTooltipModule,
2614
+ AXBreadcrumbsModule,
2615
+ AXDropdownButtonModule,
2616
+ AXSearchBoxModule,
2617
+ AXTranslationModule,
2618
+ DragDropModule,
2619
+ AXPAuthModule,
2620
+ AXPEntityMasterToolbarViewComponent,
2621
+ AXPPageLayoutComponent,
2622
+ AXPThemeLayoutBlockComponent,
2623
+ AXPThemeLayoutStartSideComponent,
2624
+ AXPEntityCategoryComponent,
2625
+ AXPEntityMasterListDataTableComponent,
2626
+ AXPEntityMasterListCardListComponent,
2627
+ ], providers: [
2628
+ {
2629
+ provide: AXPPageLayoutBase,
2630
+ useExisting: AXPEntityMasterListViewComponent,
2631
+ },
2632
+ ], template: "<axp-page-layout *translate=\"let t\">\n @if (vm.entityDef.category) {\n <axp-layout-start-side id=\"axp-entity-category-drawer\">\n <axp-entity-category\n id=\"axp-entity-category\"\n class=\"ax-w-80\"\n [vm]=\"vm\"\n [searchValue]=\"categorySearchValue()\"\n ></axp-entity-category>\n </axp-layout-start-side>\n }\n <axp-page-toolbar id=\"axp-entity-toolbar\">\n <axp-entity-master-toolbar-view [viewModel]=\"vm\"></axp-entity-master-toolbar-view>\n </axp-page-toolbar>\n <axp-page-content class=\"ax-overflow-auto ax-pt-0\">\n <div\n class=\"ax-flex ax-items-center ax-justify-between ax-gap-1 ax-pb-1 ax-text-muted ax-text-sm\"\n [class.ax-invisible]=\"!vm.selectedItems().length\"\n >\n <span\n >{{ vm.selectedItems().length }}\n <span>{{ '@general:terms.interface.items-selected' | translate | async }}</span>\n </span>\n <ax-button text=\"@general:terms.interface.unselect-all\" class=\"ax-xs\" (onClick)=\"handleUnselectAll()\"></ax-button>\n </div>\n @if (vm.enabledListLayouts().includes('table')) {\n <axp-entity-master-list-data-table\n class=\"axp-entity-list-host\"\n [class.axp-entity-list-host--hidden]=\"vm.activeListLayout() !== 'table'\"\n [vm]=\"vm\"\n (pageChanged)=\"onListPageChanged()\"\n ></axp-entity-master-list-data-table>\n }\n @if (vm.enabledListLayouts().includes('card')) {\n <axp-entity-master-list-card-list\n class=\"axp-entity-list-host\"\n [class.axp-entity-list-host--hidden]=\"vm.activeListLayout() !== 'card'\"\n [vm]=\"vm\"\n (pageChanged)=\"onListPageChanged()\"\n ></axp-entity-master-list-card-list>\n }\n </axp-page-content>\n</axp-page-layout>\n", styles: ["axp-entity-master-list axp-layout-start-side{min-width:20rem!important;border-inline-end-width:1px}axp-entity-master-list axp-layout-header{padding-bottom:.25rem!important}axp-entity-master-list .axp-entity-list-host{display:flex;height:100%;min-height:0px;flex-direction:column}axp-entity-master-list axp-page-content{display:flex;min-height:0px;flex-direction:column}axp-entity-master-list .axp-entity-list-host--hidden{display:none!important}.cdk-drag-preview{border-radius:.375rem;border-width:1px;--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background:rgba(var(--ax-color-on-surface));padding:.5rem;height:max-content!important}.collapsed-search-box{margin-top:0;height:0px;opacity:0}.view-drawer{width:85vw}@media(min-width:768px){.view-drawer{width:45vw}}@media(min-width:1024px){.view-drawer{width:35vw}}@media(min-width:1536px){.view-drawer{width:20vw}}.view-drawer{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));border-inline-start-width:1px;border-inline-start-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));border-top-width:1px;--tw-border-opacity: 1;border-top-color:rgba(var(--ax-sys-color-primary-600),var(--tw-border-opacity, 1))}.view-drawer ax-header{display:flex;align-items:center;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lighter-surface),var(--tw-bg-opacity, 1));padding:.5rem 1rem}.view-drawer ax-header h2{font-size:1.25rem;line-height:1.75rem;font-weight:700;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lighter-surface),var(--tw-text-opacity, 1))}.view-drawer ax-footer{position:absolute!important;bottom:0!important;width:100%!important;justify-content:flex-start!important;border-top-width:1px!important;--tw-border-opacity: 1 !important;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-border-opacity, 1))!important;padding:.5rem 1rem!important}\n"] }]
2633
+ }], ctorParameters: () => [{ type: i1$4.AXPlatform }], propDecorators: { dataTable: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPEntityMasterListDataTableComponent), { isSignal: true }] }], cardList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPEntityMasterListCardListComponent), { isSignal: true }] }] } });
2634
+
2635
+ var entityMasterListView_component = /*#__PURE__*/Object.freeze({
2636
+ __proto__: null,
2637
+ AXPEntityMasterListViewComponent: AXPEntityMasterListViewComponent
2638
+ });
2639
+
2640
+ class AXPRootLayoutFooterComponent {
2641
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2642
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPRootLayoutFooterComponent, isStandalone: true, selector: "axp-dashboard-admin-footer", host: { classAttribute: "ax-h-10 ax-flex ax-item-center ax-justify-between ax-bg-lighter ax-border-lighter ax-px-6 ax-border-t" }, providers: [], ngImport: i0, template: "<div class=\"ax-flex ax-items-center ax-justify-start ax-gap-1\">\n <axp-component-slot name=\"root-footer-start\"></axp-component-slot>\n</div>\n<div class=\"ax-flex ax-items-center ax-justify-end ax-gap-1\">\n <axp-component-slot name=\"root-footer-end\"></axp-component-slot>\n</div>\n", dependencies: [{ kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i1$5.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2643
+ }
2644
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutFooterComponent, decorators: [{
2645
+ type: Component,
2646
+ args: [{ selector: 'axp-dashboard-admin-footer', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [], host: {
2647
+ class: 'ax-h-10 ax-flex ax-item-center ax-justify-between ax-bg-lighter ax-border-lighter ax-px-6 ax-border-t',
2648
+ }, standalone: true, imports: [
2649
+ AXPComponentSlotModule
2650
+ ], template: "<div class=\"ax-flex ax-items-center ax-justify-start ax-gap-1\">\n <axp-component-slot name=\"root-footer-start\"></axp-component-slot>\n</div>\n<div class=\"ax-flex ax-items-center ax-justify-end ax-gap-1\">\n <axp-component-slot name=\"root-footer-end\"></axp-component-slot>\n</div>\n" }]
2651
+ }] });
2652
+
2653
+ //#region ---- Types ----
2654
+ //#endregion
2655
+ //#region ---- Layout ----
2656
+ const DEFAULT_MIN_WIDTH_PX = 88;
2657
+ const DEFAULT_MAX_WIDTH_PX = 192;
2658
+ const DEFAULT_TAB_GAP_PX = 8;
2659
+ /**
2660
+ * Chrome-like tab strip layout: equal flexible widths between min/max; horizontal scroll when needed.
2661
+ */
2662
+ function computeRouteTabBarLayout(stripWidthPx, tabs, _activeKey, options = {}) {
2663
+ const minWidthPx = options.minWidthPx ?? DEFAULT_MIN_WIDTH_PX;
2664
+ const maxWidthPx = options.maxWidthPx ?? DEFAULT_MAX_WIDTH_PX;
2665
+ const tabGapPx = options.tabGapPx ?? DEFAULT_TAB_GAP_PX;
2666
+ if (stripWidthPx <= 0 || !tabs.length) {
2667
+ return {
2668
+ tabWidthPx: minWidthPx,
2669
+ isScrollable: false,
2670
+ };
2671
+ }
2672
+ const count = tabs.length;
2673
+ const gapsForAll = Math.max(0, count - 1) * tabGapPx;
2674
+ const rawWidth = (stripWidthPx - gapsForAll) / count;
2675
+ const fittedWidth = Math.min(maxWidthPx, Math.max(minWidthPx, rawWidth));
2676
+ const needsScroll = count * fittedWidth + gapsForAll > stripWidthPx;
2677
+ const tabWidthPx = needsScroll ? minWidthPx : Math.floor(fittedWidth);
2678
+ return {
2679
+ tabWidthPx,
2680
+ isScrollable: needsScroll,
2681
+ };
2682
+ }
2683
+ //#endregion
2684
+
2685
+ const AXP_ROUTE_TAB_TITLE_TOOLTIP_OPEN_AFTER_MS = 500;
2686
+ class AXPRouteTabBarComponent {
2687
+ constructor() {
2688
+ //#region ---- Services & Dependencies ----
2689
+ this.tabNavigation = inject(AXPTabNavigationService);
2690
+ this.tabPreviewService = inject(AXPTabPreviewService);
2691
+ this.translateService = inject(AXTranslationService);
2692
+ this.host = inject((ElementRef));
2693
+ this.tabStrip = viewChild('tabStrip', ...(ngDevMode ? [{ debugName: "tabStrip" }] : /* istanbul ignore next */ []));
2694
+ this.tabsScroll = viewChild('tabsScroll', ...(ngDevMode ? [{ debugName: "tabsScroll" }] : /* istanbul ignore next */ []));
2695
+ this.contextMenu = viewChild('tabContextMenu', ...(ngDevMode ? [{ debugName: "contextMenu" }] : /* istanbul ignore next */ []));
2696
+ this.suppressTabChange = signal(false, ...(ngDevMode ? [{ debugName: "suppressTabChange" }] : /* istanbul ignore next */ []));
2697
+ this.contextTabKey = signal(null, ...(ngDevMode ? [{ debugName: "contextTabKey" }] : /* istanbul ignore next */ []));
2698
+ this.stripWidthPx = signal(0, ...(ngDevMode ? [{ debugName: "stripWidthPx" }] : /* istanbul ignore next */ []));
2699
+ this.isTabDragging = signal(false, ...(ngDevMode ? [{ debugName: "isTabDragging" }] : /* istanbul ignore next */ []));
2700
+ this.inHeader = signal(false, ...(ngDevMode ? [{ debugName: "inHeader" }] : /* istanbul ignore next */ []));
2701
+ this.tabTitleTooltipOpenAfterMs = AXP_ROUTE_TAB_TITLE_TOOLTIP_OPEN_AFTER_MS;
2702
+ //#endregion
2703
+ //#region ---- Layout State ----
2704
+ this.layout = computed(() => computeRouteTabBarLayout(this.stripWidthPx(), this.tabNavigation.tabList(), this.tabNavigation.activeKey()), ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
2705
+ this.tabWidthPx = computed(() => this.layout().tabWidthPx, ...(ngDevMode ? [{ debugName: "tabWidthPx" }] : /* istanbul ignore next */ []));
2706
+ this.hasHorizontalOverflow = signal(false, ...(ngDevMode ? [{ debugName: "hasHorizontalOverflow" }] : /* istanbul ignore next */ []));
2707
+ this.canScrollForward = signal(false, ...(ngDevMode ? [{ debugName: "canScrollForward" }] : /* istanbul ignore next */ []));
2708
+ this.canScrollBackward = signal(false, ...(ngDevMode ? [{ debugName: "canScrollBackward" }] : /* istanbul ignore next */ []));
2709
+ this.scrollForwardIcon = computed(() => this.#isTabsRtl() ? 'fa-solid fa-chevron-left' : 'fa-solid fa-chevron-right', ...(ngDevMode ? [{ debugName: "scrollForwardIcon" }] : /* istanbul ignore next */ []));
2710
+ this.scrollBackwardIcon = computed(() => this.#isTabsRtl() ? 'fa-solid fa-chevron-right' : 'fa-solid fa-chevron-left', ...(ngDevMode ? [{ debugName: "scrollBackwardIcon" }] : /* istanbul ignore next */ []));
2711
+ this.isMiddlePanning = signal(false, ...(ngDevMode ? [{ debugName: "isMiddlePanning" }] : /* istanbul ignore next */ []));
2712
+ this.middlePanSession = null;
2713
+ this.middlePanMoved = false;
2714
+ //#endregion
2715
+ //#region ---- Layout Helpers ----
2716
+ this.#onTabsScroll = () => {
2717
+ this.#updateScrollState();
2718
+ };
2719
+ this.#onMiddlePanMove = (event) => {
2720
+ const session = this.middlePanSession;
2721
+ if (!session) {
2722
+ return;
2723
+ }
2724
+ const deltaX = event.clientX - session.lastX;
2725
+ session.lastX = event.clientX;
2726
+ if (Math.abs(event.clientX - session.startX) > 2) {
2727
+ session.moved = true;
2728
+ }
2729
+ session.container.scrollBy({ left: -deltaX });
2730
+ this.#updateScrollState();
2731
+ };
2732
+ this.#onMiddlePanUp = (event) => {
2733
+ if (event.button !== 1 && event.buttons !== 0) {
2734
+ return;
2735
+ }
2736
+ this.#endMiddlePan();
2737
+ };
2738
+ //#endregion
2739
+ //#region ---- Tab Strip Sync ----
2740
+ /** Scrolls the active tab into view when the strip overflows horizontally. */
2741
+ this.#scrollActiveTabIntoView = effect(() => {
2742
+ const activeKey = this.tabNavigation.activeKey();
2743
+ const tabs = this.tabNavigation.tabList();
2744
+ this.tabWidthPx();
2745
+ this.stripWidthPx();
2746
+ if (!activeKey || !tabs.length) {
2747
+ return;
2748
+ }
2749
+ untracked(() => {
2750
+ queueMicrotask(() => {
2751
+ this.#updateScrollState();
2752
+ this.#scrollTabIntoView(activeKey);
2753
+ });
2754
+ });
2755
+ }, ...(ngDevMode ? [{ debugName: "#scrollActiveTabIntoView" }] : /* istanbul ignore next */ []));
2756
+ }
2757
+ //#endregion
2758
+ //#region ---- Lifecycle Methods ----
2759
+ ngAfterViewInit() {
2760
+ this.inHeader.set(this.host.nativeElement.classList.contains('axp-route-tab-bar--in-header'));
2761
+ const strip = this.tabStrip()?.nativeElement;
2762
+ if (!strip) {
2763
+ return;
2764
+ }
2765
+ this.stripWidthPx.set(strip.clientWidth);
2766
+ this.stripResizeObserver = new ResizeObserver(() => {
2767
+ this.stripWidthPx.set(strip.clientWidth);
2768
+ this.#updateScrollState();
2769
+ });
2770
+ this.stripResizeObserver.observe(strip);
2771
+ const scrollElement = this.tabsScroll()?.nativeElement;
2772
+ if (scrollElement) {
2773
+ this.tabsScrollElement = scrollElement;
2774
+ scrollElement.addEventListener('scroll', this.#onTabsScroll, { passive: true });
2775
+ this.tabsScrollObserver = new ResizeObserver(() => {
2776
+ this.#updateScrollState();
2777
+ });
2778
+ this.tabsScrollObserver.observe(scrollElement);
2779
+ const tabList = scrollElement.querySelector('.axp-route-tab-bar__tab-list');
2780
+ if (tabList) {
2781
+ this.tabsScrollObserver.observe(tabList);
2782
+ }
2783
+ }
2784
+ this.#updateScrollState();
2785
+ }
2786
+ ngOnDestroy() {
2787
+ this.#endMiddlePan();
2788
+ this.tabsScrollElement?.removeEventListener('scroll', this.#onTabsScroll);
2789
+ this.stripResizeObserver?.disconnect();
2790
+ this.tabsScrollObserver?.disconnect();
2791
+ }
2792
+ //#endregion
2793
+ //#region ---- Layout Helpers ----
2794
+ #onTabsScroll;
2795
+ /** Updates overflow and edge scroll affordances from the live tab strip. */
2796
+ #updateScrollState() {
2797
+ const tabsElement = this.#getTabsScrollElement();
2798
+ if (!tabsElement) {
2799
+ this.hasHorizontalOverflow.set(false);
2800
+ this.canScrollForward.set(false);
2801
+ this.canScrollBackward.set(false);
2802
+ return;
2803
+ }
2804
+ const hasOverflow = tabsElement.scrollWidth - tabsElement.clientWidth > 1;
2805
+ this.hasHorizontalOverflow.set(hasOverflow);
2806
+ if (!hasOverflow) {
2807
+ this.canScrollForward.set(false);
2808
+ this.canScrollBackward.set(false);
2809
+ return;
2810
+ }
2811
+ this.canScrollForward.set(!!this.#findScrollTarget(tabsElement, 'forward'));
2812
+ this.canScrollBackward.set(!!this.#findScrollTarget(tabsElement, 'backward'));
2813
+ }
2814
+ #getTabsScrollElement() {
2815
+ return this.tabsScrollElement ?? this.tabsScroll()?.nativeElement ?? null;
2816
+ }
2817
+ #isTabsRtl() {
2818
+ const tabsElement = this.#getTabsScrollElement();
2819
+ if (!tabsElement) {
2820
+ return false;
2821
+ }
2822
+ return getComputedStyle(tabsElement).direction === 'rtl';
2823
+ }
2824
+ #findScrollTarget(container, direction) {
2825
+ const tabs = Array.from(container.querySelectorAll('[data-tab-key]'));
2826
+ if (!tabs.length) {
2827
+ return null;
2828
+ }
2829
+ const bounds = container.getBoundingClientRect();
2830
+ const epsilon = 2;
2831
+ const isRtl = this.#isTabsRtl();
2832
+ if (direction === 'forward') {
2833
+ if (!isRtl) {
2834
+ return tabs.find((tab) => tab.getBoundingClientRect().right > bounds.right + epsilon) ?? null;
2835
+ }
2836
+ return [...tabs].reverse().find((tab) => tab.getBoundingClientRect().left < bounds.left - epsilon) ?? null;
2837
+ }
2838
+ if (!isRtl) {
2839
+ return [...tabs].reverse().find((tab) => tab.getBoundingClientRect().left < bounds.left - epsilon) ?? null;
2840
+ }
2841
+ return tabs.find((tab) => tab.getBoundingClientRect().right > bounds.right + epsilon) ?? null;
2842
+ }
2843
+ /** Scrolls the strip toward hidden tabs on the requested edge. */
2844
+ scrollTabs(direction) {
2845
+ const container = this.#getTabsScrollElement();
2846
+ if (!container) {
2847
+ return;
2848
+ }
2849
+ const target = this.#findScrollTarget(container, direction);
2850
+ target?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
2851
+ }
2852
+ /** Smoothly brings a tab header into view inside the scrollable strip. */
2853
+ #scrollTabIntoView(key) {
2854
+ const scrollElement = this.#getTabsScrollElement();
2855
+ const activeTab = scrollElement?.querySelector(`[data-tab-key="${key}"]`);
2856
+ activeTab?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
2857
+ }
2858
+ /** Starts middle-mouse drag scrolling on the tab strip. */
2859
+ onTabsScrollMouseDown(event) {
2860
+ if (event.button !== 1) {
2861
+ return;
2862
+ }
2863
+ if (event.target.closest('.axp-route-tab-bar__close')) {
2864
+ return;
2865
+ }
2866
+ const container = this.#getTabsScrollElement();
2867
+ if (!container) {
2868
+ return;
2869
+ }
2870
+ event.preventDefault();
2871
+ this.middlePanSession = {
2872
+ container,
2873
+ startX: event.clientX,
2874
+ lastX: event.clientX,
2875
+ moved: false,
2876
+ };
2877
+ this.isMiddlePanning.set(true);
2878
+ document.body.style.cursor = 'grabbing';
2879
+ window.addEventListener('mousemove', this.#onMiddlePanMove);
2880
+ window.addEventListener('mouseup', this.#onMiddlePanUp);
2881
+ }
2882
+ #onMiddlePanMove;
2883
+ #onMiddlePanUp;
2884
+ #endMiddlePan() {
2885
+ window.removeEventListener('mousemove', this.#onMiddlePanMove);
2886
+ window.removeEventListener('mouseup', this.#onMiddlePanUp);
2887
+ document.body.style.cursor = '';
2888
+ this.isMiddlePanning.set(false);
2889
+ this.middlePanMoved = this.middlePanSession?.moved ?? false;
2890
+ this.middlePanSession = null;
2891
+ }
2892
+ //#endregion
2893
+ //#region ---- UI Handlers ----
2894
+ /** Whether a tab can be dragged (home tab stays fixed at the start). */
2895
+ isTabDragDisabled(tab) {
2896
+ return tab.key === AXP_HOME_TAB_KEY;
2897
+ }
2898
+ onTabDragStarted() {
2899
+ this.isTabDragging.set(true);
2900
+ }
2901
+ onTabDragEnded() {
2902
+ queueMicrotask(() => {
2903
+ this.isTabDragging.set(false);
2904
+ });
2905
+ }
2906
+ onTabDropped(event) {
2907
+ this.tabNavigation.reorderTabs(event.previousIndex, event.currentIndex);
2908
+ }
2909
+ async onTabActivate(tab, event) {
2910
+ if (this.isTabDragging() || this.suppressTabChange()) {
2911
+ return;
2912
+ }
2913
+ if (event.defaultPrevented) {
2914
+ return;
2915
+ }
2916
+ this.#scrollTabIntoView(tab.key);
2917
+ if (tab.key === this.tabNavigation.activeKey()) {
2918
+ return;
2919
+ }
2920
+ await this.tabNavigation.activateTabKey(tab.key);
2921
+ }
2922
+ /** Resolves tab metadata for context menu actions. */
2923
+ findTab(key) {
2924
+ return this.tabNavigation.tabList().find((item) => item.key === key);
2925
+ }
2926
+ /** Resolves tab label parts from structured breadcrumb segments when available. */
2927
+ resolveTabTitleParts(tab, translatedTitle) {
2928
+ return axpResolveTabTitleParts(tab, translatedTitle);
2929
+ }
2930
+ /** Normalizes menu/page icon classes for `ax-icon`. */
2931
+ resolveTabIcon(tab) {
2932
+ const icon = tab.icon?.trim();
2933
+ if (!icon) {
2934
+ return 'fa-light fa-window';
2935
+ }
2936
+ if (icon.includes(' ')) {
2937
+ return icon;
2938
+ }
2939
+ return `fa-light ${icon}`;
2940
+ }
2941
+ /** Closes a tab when the close button is clicked. */
2942
+ async onTabCloseClick(tab, event) {
2943
+ if (!tab.closable || tab.pinned) {
2944
+ return;
2945
+ }
2946
+ event.preventDefault();
2947
+ event.stopPropagation();
2948
+ this.suppressTabChange.set(true);
2949
+ try {
2950
+ await this.tabNavigation.closeTab(tab.key);
2951
+ }
2952
+ finally {
2953
+ this.suppressTabChange.set(false);
2954
+ }
2955
+ }
2956
+ /** Closes a tab on middle mouse button click (browser-style). */
2957
+ async onTabMiddleClick(tab, event) {
2958
+ if (this.middlePanMoved) {
2959
+ this.middlePanMoved = false;
2960
+ event.preventDefault();
2961
+ event.stopPropagation();
2962
+ return;
2963
+ }
2964
+ if (event.button !== 1 || !tab.closable || tab.pinned) {
2965
+ return;
2966
+ }
2967
+ event.preventDefault();
2968
+ event.stopPropagation();
2969
+ this.suppressTabChange.set(true);
2970
+ try {
2971
+ await this.tabNavigation.closeTab(tab.key);
2972
+ }
2973
+ finally {
2974
+ this.suppressTabChange.set(false);
2975
+ }
2976
+ }
2977
+ async openTabContextMenu(event, key) {
2978
+ event.preventDefault();
2979
+ event.stopPropagation();
2980
+ this.contextTabKey.set(key);
2981
+ const tab = this.findTab(key);
2982
+ const isHomeTab = key === AXP_HOME_TAB_KEY;
2983
+ const closableTabs = this.tabNavigation.tabList().filter((item) => item.closable && !item.pinned);
2984
+ const otherClosableCount = closableTabs.filter((item) => item.key !== key).length;
2985
+ const canCloseTab = !!tab?.closable && !tab.pinned;
2986
+ const [pinText, unpinText, closeText, closeOthersText, closeAllText] = await Promise.all([
2987
+ this.translateService.translateAsync('@general:actions.pin-tab.title'),
2988
+ this.translateService.translateAsync('@general:actions.unpin-tab.title'),
2989
+ this.translateService.translateAsync('@general:actions.close-tab.title'),
2990
+ this.translateService.translateAsync('@general:actions.close-other-tabs.title'),
2991
+ this.translateService.translateAsync('@general:actions.close-all-tabs.title'),
2992
+ ]);
2993
+ const items = [
2994
+ {
2995
+ name: 'toggle-pin',
2996
+ text: tab?.pinned ? unpinText : pinText,
2997
+ icon: tab?.pinned ? 'fa-light fa-thumbtack-slash' : 'fa-light fa-thumbtack',
2998
+ disabled: isHomeTab,
2999
+ },
3000
+ {
3001
+ name: 'close',
3002
+ text: closeText,
3003
+ icon: 'fa-light fa-xmark',
3004
+ disabled: !canCloseTab,
3005
+ break: true,
3006
+ },
3007
+ {
3008
+ name: 'close-others',
3009
+ text: closeOthersText,
3010
+ icon: 'fa-light fa-clone',
3011
+ disabled: otherClosableCount === 0,
3012
+ break: true,
3013
+ },
3014
+ {
3015
+ name: 'close-all',
3016
+ text: closeAllText,
3017
+ icon: 'fa-light fa-table-columns',
3018
+ disabled: closableTabs.length === 0,
3019
+ },
3020
+ ];
3021
+ const menu = this.contextMenu();
3022
+ const targetElement = event.currentTarget;
3023
+ if (!menu) {
3024
+ return;
3025
+ }
3026
+ const openingEvent = {
3027
+ sender: menu,
3028
+ canceled: false,
3029
+ targetElement,
3030
+ items,
3031
+ };
3032
+ menu.showAt({ x: event.clientX, y: event.clientY }, targetElement, openingEvent);
3033
+ }
3034
+ async handleTabContextMenuItemClick(event) {
3035
+ const key = this.contextTabKey();
3036
+ if (!key) {
3037
+ return;
3038
+ }
3039
+ const action = event.item.name;
3040
+ this.suppressTabChange.set(true);
3041
+ try {
3042
+ switch (action) {
3043
+ case 'toggle-pin':
3044
+ this.tabNavigation.toggleTabPinned(key);
3045
+ break;
3046
+ case 'close':
3047
+ await this.tabNavigation.closeTab(key);
3048
+ break;
3049
+ case 'close-others':
3050
+ await this.tabNavigation.closeOtherTabs(key);
3051
+ break;
3052
+ case 'close-all':
3053
+ await this.tabNavigation.closeAllTabs();
3054
+ break;
3055
+ }
3056
+ }
3057
+ finally {
3058
+ this.suppressTabChange.set(false);
3059
+ this.contextTabKey.set(null);
3060
+ }
3061
+ }
3062
+ //#endregion
3063
+ //#region ---- Tab Strip Sync ----
3064
+ /** Scrolls the active tab into view when the strip overflows horizontally. */
3065
+ #scrollActiveTabIntoView;
3066
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRouteTabBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3067
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRouteTabBarComponent, isStandalone: true, selector: "axp-route-tab-bar", host: { properties: { "class.axp-route-tab-bar--scrollable": "hasHorizontalOverflow()" }, classAttribute: "axp-route-tab-bar" }, viewQueries: [{ propertyName: "tabStrip", first: true, predicate: ["tabStrip"], descendants: true, isSignal: true }, { propertyName: "tabsScroll", first: true, predicate: ["tabsScroll"], descendants: true, isSignal: true }, { propertyName: "contextMenu", first: true, predicate: ["tabContextMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (tabNavigation.isTabBarVisible()) {\n <div class=\"axp-route-tab-bar__strip\" #tabStrip [style.--axp-tab-width.px]=\"tabWidthPx()\">\n <div class=\"axp-route-tab-bar__viewport\">\n @if (canScrollBackward()) {\n <div class=\"axp-route-tab-bar__scroll-rail axp-route-tab-bar__scroll-rail--start\">\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__scroll-btn\"\n [title]=\"'@general:actions.scroll-tabs-back.title' | translate | async\"\n [attr.aria-label]=\"'@general:actions.scroll-tabs-back.title' | translate | async\"\n (click)=\"scrollTabs('backward')\"\n >\n <ax-icon [icon]=\"scrollBackwardIcon()\"></ax-icon>\n </button>\n </div>\n }\n\n <div\n class=\"axp-route-tab-bar__scroll-wrap\"\n [class.axp-route-tab-bar__scroll-wrap--overflow-start]=\"canScrollBackward()\"\n [class.axp-route-tab-bar__scroll-wrap--overflow-end]=\"canScrollForward()\"\n >\n <div\n #tabsScroll\n class=\"axp-route-tab-bar__scroll\"\n [class.axp-route-tab-bar__scroll--panning]=\"isMiddlePanning()\"\n (mousedown)=\"onTabsScrollMouseDown($event)\"\n >\n <div\n class=\"axp-route-tab-bar__tab-list\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"tabNavigation.tabList()\"\n (cdkDropListDropped)=\"onTabDropped($event)\"\n >\n @for (tab of tabNavigation.tabList(); track tab.key) {\n @let tabTitle = (tab.title | translate | async) || '';\n @let tabTitleParts = resolveTabTitleParts(tab, tabTitle);\n @let isTabDirty = !!tabPreviewService.previews()[tab.key]?.isDirty;\n @let isTabActive = tabNavigation.activeKey() === tab.key;\n <ng-template #tabTitleTooltip>\n <div class=\"axp-route-tab-bar__title-tooltip\">\n <span class=\"axp-route-tab-bar__title-tooltip-prefix\">{{ tabTitleParts.prefix }}</span>\n <span class=\"axp-route-tab-bar__title-tooltip-leaf\">{{ tabTitleParts.leaf }}</span>\n @if (isTabDirty) {\n <span class=\"axp-route-tab-bar__title-tooltip-dirty\">\n {{ '@general:terms.interface.save-status.not-saved.description' | translate | async }}\n </span>\n }\n </div>\n </ng-template>\n <div\n class=\"axp-route-tab-bar__tab\"\n [class.axp-route-tab-bar__tab--active]=\"isTabActive\"\n [class.axp-route-tab-bar__tab--dirty]=\"isTabDirty\"\n [class.axp-route-tab-bar__tab--dragging]=\"isTabDragging()\"\n [attr.data-tab-key]=\"tab.key\"\n role=\"tab\"\n [attr.aria-selected]=\"isTabActive\"\n cdkDrag\n cdkDragLockAxis=\"x\"\n [cdkDragData]=\"tab\"\n [cdkDragDisabled]=\"isTabDragDisabled(tab)\"\n (cdkDragStarted)=\"onTabDragStarted()\"\n (cdkDragEnded)=\"onTabDragEnded()\"\n (click)=\"onTabActivate(tab, $event)\"\n >\n <div\n class=\"axp-route-tab-bar__tab-inner\"\n cdkDragHandle\n [axTooltip]=\"tabTitleTooltip\"\n [axTooltipOpenAfter]=\"tabTitleTooltipOpenAfterMs\"\n [axTooltipDisabled]=\"!tabTitleParts.prefix || isTabActive\"\n axTooltipPlacement=\"bottom\"\n [attr.aria-label]=\"isTabDirty ? ('@general:terms.interface.save-status.not-saved.title' | translate | async) : null\"\n (contextmenu)=\"openTabContextMenu($event, tab.key)\"\n (auxclick)=\"onTabMiddleClick(tab, $event)\"\n >\n <span class=\"axp-route-tab-bar__tab-icon\" aria-hidden=\"true\">\n <ax-icon [icon]=\"resolveTabIcon(tab)\"></ax-icon>\n </span>\n\n <span class=\"axp-route-tab-bar__tab-title-fade\" axpRouteTabTitleOverflow>\n <span class=\"axp-route-tab-bar__tab-title\">{{ tabTitleParts.leaf }}</span>\n </span>\n\n @if (tab.pinned) {\n <span class=\"axp-route-tab-bar__pin\" aria-hidden=\"true\">\n <ax-icon icon=\"fa-solid fa-thumbtack\"></ax-icon>\n </span>\n } @else if (tab.closable) {\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__close\"\n [title]=\"'@general:actions.close-tab.title' | translate | async\"\n (click)=\"onTabCloseClick(tab, $event)\"\n >\n <ax-icon icon=\"far fa-xmark\"></ax-icon>\n </button>\n }\n </div>\n\n <ng-template cdkDragPreview>\n <div\n class=\"axp-route-tab-bar__drag-preview\"\n [class.axp-route-tab-bar__drag-preview--in-header]=\"inHeader()\"\n [class.axp-route-tab-bar__drag-preview--active]=\"isTabActive\"\n [class.axp-route-tab-bar__drag-preview--dirty]=\"isTabDirty\"\n [style.width.px]=\"tabWidthPx()\"\n >\n <div class=\"axp-route-tab-bar__drag-preview-inner\">\n <span class=\"axp-route-tab-bar__tab-icon\" aria-hidden=\"true\">\n <ax-icon [icon]=\"resolveTabIcon(tab)\"></ax-icon>\n </span>\n <span class=\"axp-route-tab-bar__tab-title\">{{ tabTitleParts.leaf }}</span>\n </div>\n </div>\n </ng-template>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (canScrollForward()) {\n <div class=\"axp-route-tab-bar__scroll-rail axp-route-tab-bar__scroll-rail--end\">\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__scroll-btn\"\n [title]=\"'@general:actions.scroll-tabs-forward.title' | translate | async\"\n [attr.aria-label]=\"'@general:actions.scroll-tabs-forward.title' | translate | async\"\n (click)=\"scrollTabs('forward')\"\n >\n <ax-icon [icon]=\"scrollForwardIcon()\"></ax-icon>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n<ax-context-menu\n #tabContextMenu\n [closeOn]=\"'click'\"\n [orientation]=\"'vertical'\"\n (onItemClick)=\"handleTabContextMenuItemClick($event)\"\n></ax-context-menu>\n", styles: ["axp-route-tab-bar{display:block;min-width:0;--axp-route-tab-bar-fade: var(--ax-sys-color-lightest-surface);--axp-route-tab-bar-chevron-size: 2rem}axp-route-tab-bar .axp-route-tab-bar__strip{display:flex;align-items:stretch;gap:.5rem;min-width:0}axp-route-tab-bar .axp-route-tab-bar__viewport{display:flex;flex-direction:row;align-items:center;flex:1 1 auto;min-width:0;height:100%;gap:.25rem}axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs,axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs .ax-tabs-scroll-area,axp-route-tab-bar .axp-route-tab-bar__viewport .axp-route-tab-bar__tab-list{overflow:visible!important;scrollbar-width:none!important;-ms-overflow-style:none!important}axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs::-webkit-scrollbar,axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs .ax-tabs-scroll-area::-webkit-scrollbar,axp-route-tab-bar .axp-route-tab-bar__viewport .axp-route-tab-bar__tab-list::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}axp-route-tab-bar .axp-route-tab-bar__scroll-rail{display:flex;flex:0 0 var(--axp-route-tab-bar-chevron-size);align-items:center;justify-content:center;align-self:center;width:var(--axp-route-tab-bar-chevron-size);min-width:var(--axp-route-tab-bar-chevron-size);height:var(--axp-route-tab-bar-tab-row-height, 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap{position:relative;flex:1 1 auto;min-width:0;height:100%;overflow:hidden}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before,axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{content:\"\";position:absolute;inset-block:0;width:1.5rem;z-index:1;pointer-events:none}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before{inset-inline-start:0;background:linear-gradient(to right,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{inset-inline-end:0;background:linear-gradient(to left,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before{background:linear-gradient(to left,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{background:linear-gradient(to right,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll{width:100%;min-width:0;height:100%;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;cursor:default;scrollbar-width:none!important;-ms-overflow-style:none!important}axp-route-tab-bar .axp-route-tab-bar__scroll::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;background:transparent!important}axp-route-tab-bar .axp-route-tab-bar__scroll.axp-route-tab-bar__scroll--panning,axp-route-tab-bar .axp-route-tab-bar__scroll.axp-route-tab-bar__scroll--panning *{cursor:grabbing!important;-webkit-user-select:none;user-select:none}axp-route-tab-bar .axp-route-tab-bar__tabs-host{height:100%;min-width:0;width:max-content}axp-route-tab-bar .axp-route-tab-bar__tab-list{display:flex;flex:none;flex-wrap:nowrap;align-items:stretch;width:max-content;min-width:max-content;height:100%;gap:.5rem}axp-route-tab-bar .axp-route-tab-bar__tab{cursor:pointer;flex:0 0 var(--axp-tab-width, 5.5rem);width:var(--axp-tab-width, 5.5rem);min-width:var(--axp-tab-width, 5.5rem);max-width:var(--axp-tab-width, 5.5rem);height:100%;transform:translateY(0);transition:background-color .15s ease,color .15s ease,border-color .15s ease,transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-disabled{cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-dragging,axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-dragging .axp-route-tab-bar__tab-inner{cursor:grabbing}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-placeholder{opacity:.35}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-animating{transition:transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__tab-list.cdk-drop-list-dragging .axp-route-tab-bar__tab:not(.cdk-drag-placeholder){transition:transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__scroll-btn{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin:0;padding:0;border:none;background:transparent;color:inherit;cursor:pointer;opacity:.88;border-radius:var(--ax-sys-border-radius);transition:opacity .15s ease}axp-route-tab-bar .axp-route-tab-bar__scroll-btn ax-icon{font-size:.75rem}axp-route-tab-bar .axp-route-tab-bar__scroll-btn:hover{opacity:1}axp-route-tab-bar .axp-route-tab-bar__scroll-btn:focus-visible{opacity:1;outline:2px solid rgb(var(--ax-sys-color-primary-500));outline-offset:1px}axp-route-tab-bar .axp-route-tab-bar__tab-inner{position:relative;display:flex;justify-content:space-between;width:100%;align-items:center;gap:.375rem;min-width:0;max-width:100%;height:100%;padding-inline:.625rem;cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab-inner.cdk-drag-handle{cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab--dirty{position:relative}axp-route-tab-bar .axp-route-tab-bar__tab--dirty:before{content:\"\";position:absolute;inset:0;z-index:1;border-radius:inherit;border-top:2px solid rgb(var(--ax-sys-color-warning-500));pointer-events:none}axp-route-tab-bar .axp-route-tab-bar__tab-icon{display:inline-flex;flex-shrink:0}axp-route-tab-bar .axp-route-tab-bar__tab-icon ax-icon{font-size:.875rem}axp-route-tab-bar .axp-route-tab-bar__tab-title-fade{position:relative;flex:1 1 auto;min-width:0;max-width:100%;overflow:hidden}axp-route-tab-bar .axp-route-tab-bar__tab-title{display:block;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:.875rem;line-height:1.25rem}axp-route-tab-bar .axp-route-tab-bar__tab-title-fade--overflowing .axp-route-tab-bar__tab-title{-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 1.25rem),transparent 100%);mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 1.25rem),transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__tab-title-fade--overflowing .axp-route-tab-bar__tab-title{-webkit-mask-image:linear-gradient(to left,#000 0,#000 calc(100% - 1.25rem),transparent 100%);mask-image:linear-gradient(to left,#000 0,#000 calc(100% - 1.25rem),transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__pin{display:inline-flex;flex-shrink:0;color:inherit}axp-route-tab-bar .axp-route-tab-bar__pin ax-icon{font-size:.6875rem}axp-route-tab-bar .axp-route-tab-bar__close{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:0;height:1.25rem;margin:0;padding:0;overflow:hidden;border:none;background:transparent;color:inherit;border-radius:var(--ax-sys-border-radius);opacity:0;pointer-events:none;cursor:pointer;transition:opacity .15s ease,width .15s ease,background-color .15s ease}axp-route-tab-bar .axp-route-tab-bar__close ax-icon{font-size:.75rem}axp-route-tab-bar .axp-route-tab-bar__tab:hover .axp-route-tab-bar__close,axp-route-tab-bar .axp-route-tab-bar__close:focus-visible{width:1.25rem;opacity:.72;pointer-events:auto}axp-route-tab-bar .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header){border-bottom:1px solid rgb(var(--ax-sys-color-border-light-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__strip{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));padding-inline:.5rem}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab:hover:not(.axp-route-tab-bar__tab--active){background-color:rgb(var(--ax-sys-color-light-surface));color:rgb(var(--ax-sys-color-on-light-surface));border-color:rgb(var(--ax-sys-color-border-light-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header{align-self:stretch;height:100%;--axp-route-tab-bar-fade: var(--ax-sys-color-primary-surface);--axp-route-tab-bar-tab-row-height: 2.25rem}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__strip{display:flex;align-self:stretch;height:100%;align-items:stretch;background:transparent;color:rgb(var(--ax-sys-color-on-primary-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__viewport{align-items:flex-end}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-rail{align-self:flex-end;margin-bottom:0}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-wrap--overflow-start:before,axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-wrap--overflow-end:after{inset-block-end:0;inset-block-start:auto;height:var(--axp-route-tab-bar-tab-row-height)}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll{cursor:default}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab-list{align-self:stretch;align-items:flex-end;min-height:0;padding-top:1rem}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab{align-self:center;padding:0;border-top-left-radius:.5rem;border-top-right-radius:.5rem;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:rgb(var(--ax-sys-color-primary-dark-surface));color:rgb(var(--ax-sys-color-on-primary-dark-surface));border-color:rgb(var(--ax-sys-color-border-primary-dark-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab ax-icon{color:inherit}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab:hover:not(.axp-route-tab-bar__tab--active){background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active:is(.ax-dark *){background-color:rgb(var(--ax-sys-color-primary-darkest-surface));color:rgb(var(--ax-sys-color-on-primary-darkest-surface));border-color:rgb(var(--ax-sys-color-border-primary-darkest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview{display:flex;align-items:center;box-sizing:border-box;height:var(--axp-route-tab-bar-tab-row-height, 2.25rem);opacity:.96;cursor:grabbing;box-shadow:0 4px 12px rgb(var(--ax-sys-color-dark)/.16),0 1px 3px rgb(var(--ax-sys-color-dark)/.12)}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__drag-preview-inner{display:flex;align-items:center;gap:.5rem;width:100%;min-width:0;height:100%;padding-inline:.625rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-icon{display:inline-flex;flex-shrink:0}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-icon ax-icon{font-size:.875rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--dirty{position:relative}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--dirty:before{content:\"\";position:absolute;inset:0;z-index:1;border-radius:inherit;border-top:2px solid rgb(var(--ax-sys-color-warning-500));pointer-events:none}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header{border-top-left-radius:.5rem;border-top-right-radius:.5rem;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:rgb(var(--ax-sys-color-primary-dark-surface));color:rgb(var(--ax-sys-color-on-primary-dark-surface));border-color:rgb(var(--ax-sys-color-border-primary-dark-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header.axp-route-tab-bar__drag-preview--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header.axp-route-tab-bar__drag-preview--active:is(.ax-dark *){background-color:rgb(var(--ax-sys-color-primary-darkest-surface));color:rgb(var(--ax-sys-color-on-primary-darkest-surface));border-color:rgb(var(--ax-sys-color-border-primary-darkest-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview:not(.axp-route-tab-bar__drag-preview--in-header){border-radius:.375rem;background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview:not(.axp-route-tab-bar__drag-preview--in-header).axp-route-tab-bar__drag-preview--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}.axp-route-tab-bar__title-tooltip{display:flex;flex-direction:column;gap:.25rem;max-width:20rem;padding:.75rem 1rem;text-align:start;border-radius:var(--ax-sys-border-radius);background:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));box-shadow:0 4px 12px rgb(var(--ax-sys-color-dark)/.12),0 1px 3px rgb(var(--ax-sys-color-dark)/.08)}.axp-route-tab-bar__title-tooltip-prefix{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.6875rem;line-height:1rem;opacity:.82}.axp-route-tab-bar__title-tooltip-leaf{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:500}.axp-route-tab-bar__title-tooltip-dirty{margin-top:.25rem;padding-top:.5rem;border-top:1px solid rgb(var(--ax-sys-color-border-light-surface));font-size:.75rem;line-height:1.125rem;color:rgb(var(--ax-sys-color-warning-600))}\n"], dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "directive", type: i2$3.AXTooltipDirective, selector: "[axTooltip]", inputs: ["axTooltipDisabled", "axTooltip", "axTooltipContext", "axTooltipPlacement", "axTooltipOffsetX", "axTooltipOffsetY", "axTooltipOpenAfter", "axTooltipCloseAfter"] }, { kind: "component", type: AXContextMenuComponent, selector: "ax-context-menu", inputs: ["orientation", "openOn", "closeOn", "closeOnRouteChange", "items", "target"], outputs: ["onItemClick", "onOpening", "onClose"] }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3068
+ }
3069
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRouteTabBarComponent, decorators: [{
3070
+ type: Component,
3071
+ args: [{ selector: 'axp-route-tab-bar', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
3072
+ class: 'axp-route-tab-bar',
3073
+ '[class.axp-route-tab-bar--scrollable]': 'hasHorizontalOverflow()',
3074
+ }, imports: [
3075
+ CdkDropList,
3076
+ CdkDrag,
3077
+ CdkDragHandle,
3078
+ CdkDragPreview,
3079
+ AXDecoratorModule,
3080
+ AXTranslationModule,
3081
+ AXTooltipModule,
3082
+ AXContextMenuComponent,
3083
+ AsyncPipe,
3084
+ ], template: "@if (tabNavigation.isTabBarVisible()) {\n <div class=\"axp-route-tab-bar__strip\" #tabStrip [style.--axp-tab-width.px]=\"tabWidthPx()\">\n <div class=\"axp-route-tab-bar__viewport\">\n @if (canScrollBackward()) {\n <div class=\"axp-route-tab-bar__scroll-rail axp-route-tab-bar__scroll-rail--start\">\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__scroll-btn\"\n [title]=\"'@general:actions.scroll-tabs-back.title' | translate | async\"\n [attr.aria-label]=\"'@general:actions.scroll-tabs-back.title' | translate | async\"\n (click)=\"scrollTabs('backward')\"\n >\n <ax-icon [icon]=\"scrollBackwardIcon()\"></ax-icon>\n </button>\n </div>\n }\n\n <div\n class=\"axp-route-tab-bar__scroll-wrap\"\n [class.axp-route-tab-bar__scroll-wrap--overflow-start]=\"canScrollBackward()\"\n [class.axp-route-tab-bar__scroll-wrap--overflow-end]=\"canScrollForward()\"\n >\n <div\n #tabsScroll\n class=\"axp-route-tab-bar__scroll\"\n [class.axp-route-tab-bar__scroll--panning]=\"isMiddlePanning()\"\n (mousedown)=\"onTabsScrollMouseDown($event)\"\n >\n <div\n class=\"axp-route-tab-bar__tab-list\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"tabNavigation.tabList()\"\n (cdkDropListDropped)=\"onTabDropped($event)\"\n >\n @for (tab of tabNavigation.tabList(); track tab.key) {\n @let tabTitle = (tab.title | translate | async) || '';\n @let tabTitleParts = resolveTabTitleParts(tab, tabTitle);\n @let isTabDirty = !!tabPreviewService.previews()[tab.key]?.isDirty;\n @let isTabActive = tabNavigation.activeKey() === tab.key;\n <ng-template #tabTitleTooltip>\n <div class=\"axp-route-tab-bar__title-tooltip\">\n <span class=\"axp-route-tab-bar__title-tooltip-prefix\">{{ tabTitleParts.prefix }}</span>\n <span class=\"axp-route-tab-bar__title-tooltip-leaf\">{{ tabTitleParts.leaf }}</span>\n @if (isTabDirty) {\n <span class=\"axp-route-tab-bar__title-tooltip-dirty\">\n {{ '@general:terms.interface.save-status.not-saved.description' | translate | async }}\n </span>\n }\n </div>\n </ng-template>\n <div\n class=\"axp-route-tab-bar__tab\"\n [class.axp-route-tab-bar__tab--active]=\"isTabActive\"\n [class.axp-route-tab-bar__tab--dirty]=\"isTabDirty\"\n [class.axp-route-tab-bar__tab--dragging]=\"isTabDragging()\"\n [attr.data-tab-key]=\"tab.key\"\n role=\"tab\"\n [attr.aria-selected]=\"isTabActive\"\n cdkDrag\n cdkDragLockAxis=\"x\"\n [cdkDragData]=\"tab\"\n [cdkDragDisabled]=\"isTabDragDisabled(tab)\"\n (cdkDragStarted)=\"onTabDragStarted()\"\n (cdkDragEnded)=\"onTabDragEnded()\"\n (click)=\"onTabActivate(tab, $event)\"\n >\n <div\n class=\"axp-route-tab-bar__tab-inner\"\n cdkDragHandle\n [axTooltip]=\"tabTitleTooltip\"\n [axTooltipOpenAfter]=\"tabTitleTooltipOpenAfterMs\"\n [axTooltipDisabled]=\"!tabTitleParts.prefix || isTabActive\"\n axTooltipPlacement=\"bottom\"\n [attr.aria-label]=\"isTabDirty ? ('@general:terms.interface.save-status.not-saved.title' | translate | async) : null\"\n (contextmenu)=\"openTabContextMenu($event, tab.key)\"\n (auxclick)=\"onTabMiddleClick(tab, $event)\"\n >\n <span class=\"axp-route-tab-bar__tab-icon\" aria-hidden=\"true\">\n <ax-icon [icon]=\"resolveTabIcon(tab)\"></ax-icon>\n </span>\n\n <span class=\"axp-route-tab-bar__tab-title-fade\" axpRouteTabTitleOverflow>\n <span class=\"axp-route-tab-bar__tab-title\">{{ tabTitleParts.leaf }}</span>\n </span>\n\n @if (tab.pinned) {\n <span class=\"axp-route-tab-bar__pin\" aria-hidden=\"true\">\n <ax-icon icon=\"fa-solid fa-thumbtack\"></ax-icon>\n </span>\n } @else if (tab.closable) {\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__close\"\n [title]=\"'@general:actions.close-tab.title' | translate | async\"\n (click)=\"onTabCloseClick(tab, $event)\"\n >\n <ax-icon icon=\"far fa-xmark\"></ax-icon>\n </button>\n }\n </div>\n\n <ng-template cdkDragPreview>\n <div\n class=\"axp-route-tab-bar__drag-preview\"\n [class.axp-route-tab-bar__drag-preview--in-header]=\"inHeader()\"\n [class.axp-route-tab-bar__drag-preview--active]=\"isTabActive\"\n [class.axp-route-tab-bar__drag-preview--dirty]=\"isTabDirty\"\n [style.width.px]=\"tabWidthPx()\"\n >\n <div class=\"axp-route-tab-bar__drag-preview-inner\">\n <span class=\"axp-route-tab-bar__tab-icon\" aria-hidden=\"true\">\n <ax-icon [icon]=\"resolveTabIcon(tab)\"></ax-icon>\n </span>\n <span class=\"axp-route-tab-bar__tab-title\">{{ tabTitleParts.leaf }}</span>\n </div>\n </div>\n </ng-template>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (canScrollForward()) {\n <div class=\"axp-route-tab-bar__scroll-rail axp-route-tab-bar__scroll-rail--end\">\n <button\n type=\"button\"\n class=\"axp-route-tab-bar__scroll-btn\"\n [title]=\"'@general:actions.scroll-tabs-forward.title' | translate | async\"\n [attr.aria-label]=\"'@general:actions.scroll-tabs-forward.title' | translate | async\"\n (click)=\"scrollTabs('forward')\"\n >\n <ax-icon [icon]=\"scrollForwardIcon()\"></ax-icon>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n<ax-context-menu\n #tabContextMenu\n [closeOn]=\"'click'\"\n [orientation]=\"'vertical'\"\n (onItemClick)=\"handleTabContextMenuItemClick($event)\"\n></ax-context-menu>\n", styles: ["axp-route-tab-bar{display:block;min-width:0;--axp-route-tab-bar-fade: var(--ax-sys-color-lightest-surface);--axp-route-tab-bar-chevron-size: 2rem}axp-route-tab-bar .axp-route-tab-bar__strip{display:flex;align-items:stretch;gap:.5rem;min-width:0}axp-route-tab-bar .axp-route-tab-bar__viewport{display:flex;flex-direction:row;align-items:center;flex:1 1 auto;min-width:0;height:100%;gap:.25rem}axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs,axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs .ax-tabs-scroll-area,axp-route-tab-bar .axp-route-tab-bar__viewport .axp-route-tab-bar__tab-list{overflow:visible!important;scrollbar-width:none!important;-ms-overflow-style:none!important}axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs::-webkit-scrollbar,axp-route-tab-bar .axp-route-tab-bar__viewport ax-tabs .ax-tabs-scroll-area::-webkit-scrollbar,axp-route-tab-bar .axp-route-tab-bar__viewport .axp-route-tab-bar__tab-list::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}axp-route-tab-bar .axp-route-tab-bar__scroll-rail{display:flex;flex:0 0 var(--axp-route-tab-bar-chevron-size);align-items:center;justify-content:center;align-self:center;width:var(--axp-route-tab-bar-chevron-size);min-width:var(--axp-route-tab-bar-chevron-size);height:var(--axp-route-tab-bar-tab-row-height, 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap{position:relative;flex:1 1 auto;min-width:0;height:100%;overflow:hidden}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before,axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{content:\"\";position:absolute;inset-block:0;width:1.5rem;z-index:1;pointer-events:none}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before{inset-inline-start:0;background:linear-gradient(to right,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{inset-inline-end:0;background:linear-gradient(to left,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-start:before{background:linear-gradient(to left,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__scroll-wrap--overflow-end:after{background:linear-gradient(to right,rgb(var(--axp-route-tab-bar-fade)) 0%,rgb(var(--axp-route-tab-bar-fade)/.72) 35%,transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__scroll{width:100%;min-width:0;height:100%;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;cursor:default;scrollbar-width:none!important;-ms-overflow-style:none!important}axp-route-tab-bar .axp-route-tab-bar__scroll::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;background:transparent!important}axp-route-tab-bar .axp-route-tab-bar__scroll.axp-route-tab-bar__scroll--panning,axp-route-tab-bar .axp-route-tab-bar__scroll.axp-route-tab-bar__scroll--panning *{cursor:grabbing!important;-webkit-user-select:none;user-select:none}axp-route-tab-bar .axp-route-tab-bar__tabs-host{height:100%;min-width:0;width:max-content}axp-route-tab-bar .axp-route-tab-bar__tab-list{display:flex;flex:none;flex-wrap:nowrap;align-items:stretch;width:max-content;min-width:max-content;height:100%;gap:.5rem}axp-route-tab-bar .axp-route-tab-bar__tab{cursor:pointer;flex:0 0 var(--axp-tab-width, 5.5rem);width:var(--axp-tab-width, 5.5rem);min-width:var(--axp-tab-width, 5.5rem);max-width:var(--axp-tab-width, 5.5rem);height:100%;transform:translateY(0);transition:background-color .15s ease,color .15s ease,border-color .15s ease,transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-disabled{cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-dragging,axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-dragging .axp-route-tab-bar__tab-inner{cursor:grabbing}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-placeholder{opacity:.35}axp-route-tab-bar .axp-route-tab-bar__tab.cdk-drag-animating{transition:transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__tab-list.cdk-drop-list-dragging .axp-route-tab-bar__tab:not(.cdk-drag-placeholder){transition:transform .22s cubic-bezier(.4,0,.2,1)}axp-route-tab-bar .axp-route-tab-bar__scroll-btn{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin:0;padding:0;border:none;background:transparent;color:inherit;cursor:pointer;opacity:.88;border-radius:var(--ax-sys-border-radius);transition:opacity .15s ease}axp-route-tab-bar .axp-route-tab-bar__scroll-btn ax-icon{font-size:.75rem}axp-route-tab-bar .axp-route-tab-bar__scroll-btn:hover{opacity:1}axp-route-tab-bar .axp-route-tab-bar__scroll-btn:focus-visible{opacity:1;outline:2px solid rgb(var(--ax-sys-color-primary-500));outline-offset:1px}axp-route-tab-bar .axp-route-tab-bar__tab-inner{position:relative;display:flex;justify-content:space-between;width:100%;align-items:center;gap:.375rem;min-width:0;max-width:100%;height:100%;padding-inline:.625rem;cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab-inner.cdk-drag-handle{cursor:pointer}axp-route-tab-bar .axp-route-tab-bar__tab--dirty{position:relative}axp-route-tab-bar .axp-route-tab-bar__tab--dirty:before{content:\"\";position:absolute;inset:0;z-index:1;border-radius:inherit;border-top:2px solid rgb(var(--ax-sys-color-warning-500));pointer-events:none}axp-route-tab-bar .axp-route-tab-bar__tab-icon{display:inline-flex;flex-shrink:0}axp-route-tab-bar .axp-route-tab-bar__tab-icon ax-icon{font-size:.875rem}axp-route-tab-bar .axp-route-tab-bar__tab-title-fade{position:relative;flex:1 1 auto;min-width:0;max-width:100%;overflow:hidden}axp-route-tab-bar .axp-route-tab-bar__tab-title{display:block;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:.875rem;line-height:1.25rem}axp-route-tab-bar .axp-route-tab-bar__tab-title-fade--overflowing .axp-route-tab-bar__tab-title{-webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 1.25rem),transparent 100%);mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 1.25rem),transparent 100%)}[dir=rtl] axp-route-tab-bar .axp-route-tab-bar__tab-title-fade--overflowing .axp-route-tab-bar__tab-title{-webkit-mask-image:linear-gradient(to left,#000 0,#000 calc(100% - 1.25rem),transparent 100%);mask-image:linear-gradient(to left,#000 0,#000 calc(100% - 1.25rem),transparent 100%)}axp-route-tab-bar .axp-route-tab-bar__pin{display:inline-flex;flex-shrink:0;color:inherit}axp-route-tab-bar .axp-route-tab-bar__pin ax-icon{font-size:.6875rem}axp-route-tab-bar .axp-route-tab-bar__close{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:0;height:1.25rem;margin:0;padding:0;overflow:hidden;border:none;background:transparent;color:inherit;border-radius:var(--ax-sys-border-radius);opacity:0;pointer-events:none;cursor:pointer;transition:opacity .15s ease,width .15s ease,background-color .15s ease}axp-route-tab-bar .axp-route-tab-bar__close ax-icon{font-size:.75rem}axp-route-tab-bar .axp-route-tab-bar__tab:hover .axp-route-tab-bar__close,axp-route-tab-bar .axp-route-tab-bar__close:focus-visible{width:1.25rem;opacity:.72;pointer-events:auto}axp-route-tab-bar .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header){border-bottom:1px solid rgb(var(--ax-sys-color-border-light-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__strip{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));padding-inline:.5rem}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab:hover:not(.axp-route-tab-bar__tab--active){background-color:rgb(var(--ax-sys-color-light-surface));color:rgb(var(--ax-sys-color-on-light-surface));border-color:rgb(var(--ax-sys-color-border-light-surface))}axp-route-tab-bar:not(.axp-route-tab-bar--in-header) .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header{align-self:stretch;height:100%;--axp-route-tab-bar-fade: var(--ax-sys-color-primary-surface);--axp-route-tab-bar-tab-row-height: 2.25rem}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__strip{display:flex;align-self:stretch;height:100%;align-items:stretch;background:transparent;color:rgb(var(--ax-sys-color-on-primary-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__viewport{align-items:flex-end}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-rail{align-self:flex-end;margin-bottom:0}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-wrap--overflow-start:before,axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll-wrap--overflow-end:after{inset-block-end:0;inset-block-start:auto;height:var(--axp-route-tab-bar-tab-row-height)}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__scroll{cursor:default}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab-list{align-self:stretch;align-items:flex-end;min-height:0;padding-top:1rem}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab{align-self:center;padding:0;border-top-left-radius:.5rem;border-top-right-radius:.5rem;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:rgb(var(--ax-sys-color-primary-dark-surface));color:rgb(var(--ax-sys-color-on-primary-dark-surface));border-color:rgb(var(--ax-sys-color-border-primary-dark-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab ax-icon{color:inherit}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab:hover:not(.axp-route-tab-bar__tab--active){background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active:is(.ax-dark *){background-color:rgb(var(--ax-sys-color-primary-darkest-surface));color:rgb(var(--ax-sys-color-on-primary-darkest-surface));border-color:rgb(var(--ax-sys-color-border-primary-darkest-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}axp-route-tab-bar.axp-route-tab-bar--in-header .axp-route-tab-bar__tab.axp-route-tab-bar__tab--active .axp-route-tab-bar__close:hover{background-color:rgb(var(--ax-sys-color-primary-light-surface));color:rgb(var(--ax-sys-color-on-primary-light-surface));border-color:rgb(var(--ax-sys-color-border-primary-light-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview{display:flex;align-items:center;box-sizing:border-box;height:var(--axp-route-tab-bar-tab-row-height, 2.25rem);opacity:.96;cursor:grabbing;box-shadow:0 4px 12px rgb(var(--ax-sys-color-dark)/.16),0 1px 3px rgb(var(--ax-sys-color-dark)/.12)}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__drag-preview-inner{display:flex;align-items:center;gap:.5rem;width:100%;min-width:0;height:100%;padding-inline:.625rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-icon{display:inline-flex;flex-shrink:0}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-icon ax-icon{font-size:.875rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview .axp-route-tab-bar__tab-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--dirty{position:relative}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--dirty:before{content:\"\";position:absolute;inset:0;z-index:1;border-radius:inherit;border-top:2px solid rgb(var(--ax-sys-color-warning-500));pointer-events:none}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header{border-top-left-radius:.5rem;border-top-right-radius:.5rem;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:rgb(var(--ax-sys-color-primary-dark-surface));color:rgb(var(--ax-sys-color-on-primary-dark-surface));border-color:rgb(var(--ax-sys-color-border-primary-dark-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header.axp-route-tab-bar__drag-preview--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview.axp-route-tab-bar__drag-preview--in-header.axp-route-tab-bar__drag-preview--active:is(.ax-dark *){background-color:rgb(var(--ax-sys-color-primary-darkest-surface));color:rgb(var(--ax-sys-color-on-primary-darkest-surface));border-color:rgb(var(--ax-sys-color-border-primary-darkest-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview:not(.axp-route-tab-bar__drag-preview--in-header){border-radius:.375rem;background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}.cdk-drag-preview.axp-route-tab-bar__drag-preview:not(.axp-route-tab-bar__drag-preview--in-header).axp-route-tab-bar__drag-preview--active{background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface))}.axp-route-tab-bar__title-tooltip{display:flex;flex-direction:column;gap:.25rem;max-width:20rem;padding:.75rem 1rem;text-align:start;border-radius:var(--ax-sys-border-radius);background:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));box-shadow:0 4px 12px rgb(var(--ax-sys-color-dark)/.12),0 1px 3px rgb(var(--ax-sys-color-dark)/.08)}.axp-route-tab-bar__title-tooltip-prefix{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.6875rem;line-height:1rem;opacity:.82}.axp-route-tab-bar__title-tooltip-leaf{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:500}.axp-route-tab-bar__title-tooltip-dirty{margin-top:.25rem;padding-top:.5rem;border-top:1px solid rgb(var(--ax-sys-color-border-light-surface));font-size:.75rem;line-height:1.125rem;color:rgb(var(--ax-sys-color-warning-600))}\n"] }]
3085
+ }], propDecorators: { tabStrip: [{ type: i0.ViewChild, args: ['tabStrip', { isSignal: true }] }], tabsScroll: [{ type: i0.ViewChild, args: ['tabsScroll', { isSignal: true }] }], contextMenu: [{ type: i0.ViewChild, args: ['tabContextMenu', { isSignal: true }] }] } });
3086
+
3087
+ class AXPRootLayoutHeaderComponent {
3088
+ //#endregion
3089
+ //#region ---- Lifecycle Methods ----
3090
+ constructor() {
3091
+ //#region ---- Services & Dependencies ----
3092
+ this.config = inject(AXP_PLATFORM_CONFIG_TOKEN);
3093
+ this.router = inject(Router);
3094
+ this.destroyRef = inject(DestroyRef);
3095
+ this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
3096
+ this.store = inject(AXPLayoutThemeService);
3097
+ this.tabNavigation = inject(AXPTabNavigationService);
3098
+ this.logo = this.config.logo;
3099
+ this.shortcutRegistry.register({
3100
+ owner: this.destroyRef,
3101
+ id: 'root-layout-toggle-side-menu',
3102
+ priority: AXPKeyboardShortcutPriority.Global,
3103
+ group: '@general:keyboard-shortcuts.groups.global',
3104
+ shortcuts: [
3105
+ {
3106
+ keys: ['ctrl+shift+b'],
3107
+ title: '@general:keyboard-shortcuts.actions.toggle-side-menu',
3108
+ allowInEditableFields: true,
3109
+ handler: () => {
3110
+ this.toggleSideMenu();
3111
+ },
3112
+ },
3113
+ ],
3114
+ });
3115
+ }
3116
+ //#endregion
3117
+ //#region ---- UI Handlers ----
3118
+ toggleSideMenu() {
3119
+ this.store.toggleSideMenu();
3120
+ }
3121
+ handleLogoClick() {
3122
+ this.router.navigate(['/']);
3123
+ }
3124
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3125
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRootLayoutHeaderComponent, isStandalone: true, selector: "axp-dashboard-admin-header", host: { classAttribute: "ax-w-full ax-flex ax-min-w-0 ax-items-center ax-gap-2 ax-bg-primary-surface ax-text-primary-on-surface ax-h-14 ax-px-2 ax-light" }, ngImport: i0, template: "<ax-button id=\"axp-menu-toggle\" (onClick)=\"toggleSideMenu()\" color=\"primary\">\n <ax-icon class=\"rtl:ax-rotate-180\">\n <svg width=\"28\" height=\"28\" viewBox=\"0 0 30 30\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n @if (store.isSideMenuOpen()) {\n <defs>\n <clipPath id=\"clip1382_20239\">\n <rect width=\"30.000000\" height=\"30.000000\" fill=\"white\" fill-opacity=\"0\"></rect>\n </clipPath>\n </defs>\n <rect width=\"30.000000\" height=\"30.000000\" fill=\"#FFFFFF\" fill-opacity=\"0\"></rect>\n <g clip-path=\"url(#clip1382_20239)\">\n <rect\n id=\"rect\"\n x=\"17.420410\"\n y=\"12.316406\"\n rx=\"1.000947\"\n width=\"5.995172\"\n height=\"2.001895\"\n transform=\"rotate(137.159 17.420410 12.316406)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <rect\n id=\"rect\"\n x=\"12.959473\"\n y=\"13.728516\"\n rx=\"0.995190\"\n width=\"6.002943\"\n height=\"1.990380\"\n transform=\"rotate(40.853 12.959473 13.728516)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <path\n id=\"path\"\n d=\"M20.1 25.5L9.9 25.51C9.48 25.51 9.06 25.47 8.65 25.39C8.24 25.3 7.84 25.18 7.45 25.02C7.06 24.86 6.69 24.66 6.34 24.43C5.99 24.19 5.67 23.92 5.37 23.63C5.07 23.33 4.81 23 4.58 22.65C4.34 22.3 4.15 21.93 3.98 21.54C3.82 21.15 3.7 20.75 3.62 20.34C3.54 19.93 3.5 19.51 3.5 19.09L3.5 10.93C3.5 10.51 3.54 10.1 3.62 9.68C3.7 9.27 3.82 8.87 3.98 8.48C4.15 8.09 4.34 7.72 4.58 7.37C4.81 7.02 5.07 6.69 5.37 6.39C5.67 6.1 5.99 5.83 6.34 5.6C6.69 5.36 7.06 5.16 7.45 5C7.84 4.84 8.24 4.72 8.65 4.64C9.06 4.55 9.48 4.51 9.9 4.51L20.1 4.5C20.52 4.5 20.94 4.54 21.35 4.62C21.76 4.7 22.16 4.83 22.55 4.99C22.94 5.15 23.31 5.35 23.66 5.58C24 5.82 24.33 6.08 24.62 6.38C24.92 6.68 25.19 7 25.42 7.35C25.65 7.7 25.85 8.07 26.01 8.46C26.17 8.85 26.3 9.25 26.38 9.67C26.46 10.08 26.5 10.5 26.5 10.92L26.5 19.07C26.5 19.5 26.46 19.91 26.38 20.32C26.3 20.74 26.17 21.14 26.01 21.53C25.85 21.92 25.65 22.29 25.42 22.64C25.19 22.99 24.92 23.31 24.62 23.61C24.33 23.91 24 24.17 23.66 24.41C23.31 24.64 22.94 24.84 22.55 25C22.16 25.16 21.76 25.29 21.35 25.37C20.94 25.45 20.52 25.5 20.1 25.5ZM9.9 6.6C9.61 6.6 9.33 6.63 9.05 6.69C8.78 6.74 8.51 6.82 8.24 6.93C7.98 7.04 7.73 7.18 7.5 7.33C7.26 7.49 7.04 7.67 6.84 7.87C6.64 8.07 6.46 8.29 6.31 8.53C6.15 8.77 6.02 9.01 5.91 9.28C5.8 9.54 5.72 9.81 5.66 10.09C5.61 10.37 5.58 10.65 5.58 10.93L5.58 19.09C5.58 19.37 5.61 19.65 5.66 19.93C5.72 20.21 5.8 20.48 5.91 20.74C6.02 21.01 6.15 21.26 6.31 21.49C6.46 21.73 6.64 21.95 6.84 22.15C7.04 22.35 7.26 22.53 7.5 22.69C7.73 22.85 7.98 22.98 8.24 23.09C8.51 23.2 8.78 23.28 9.05 23.33C9.33 23.39 9.61 23.42 9.9 23.42L20.1 23.41C20.38 23.41 20.67 23.37 20.94 23.32C21.22 23.26 21.49 23.18 21.75 23.07C22.01 22.96 22.26 22.83 22.5 22.67C22.73 22.51 22.95 22.33 23.15 22.13C23.35 21.93 23.53 21.71 23.69 21.48C23.85 21.24 23.98 20.99 24.09 20.73C24.2 20.47 24.28 20.2 24.33 19.92C24.39 19.64 24.42 19.36 24.42 19.07L24.42 10.92C24.42 10.64 24.39 10.35 24.33 10.07C24.28 9.79 24.2 9.52 24.09 9.26C23.98 9 23.85 8.75 23.69 8.51C23.53 8.28 23.35 8.06 23.15 7.86C22.95 7.66 22.73 7.48 22.5 7.32C22.26 7.16 22.01 7.03 21.75 6.92C21.49 6.81 21.22 6.73 20.94 6.67C20.67 6.62 20.38 6.59 20.1 6.59L9.9 6.6Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"nonzero\"\n ></path>\n <path\n id=\"rect\"\n d=\"M8.5 5.51L10.54 5.51L10.6 24.43L8.55 24.43L8.5 5.51Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"evenodd\"\n ></path>\n </g>\n } @else {\n <defs>\n <clipPath id=\"clip1381_20236\">\n <rect id=\"\u6253\u5F00\u8FB9\u680F0730\" width=\"30.000000\" height=\"30.000000\" fill=\"white\" fill-opacity=\"0\"></rect>\n </clipPath>\n </defs>\n <rect id=\"\u6253\u5F00\u8FB9\u680F0730\" width=\"30.000000\" height=\"30.000000\" fill=\"#FFFFFF\" fill-opacity=\"0\"></rect>\n <g clip-path=\"url(#clip1381_20236)\">\n <rect\n id=\"rect\"\n x=\"11.572754\"\n y=\"17.683594\"\n rx=\"1.000947\"\n width=\"5.995172\"\n height=\"2.001895\"\n transform=\"rotate(-42.841 11.572754 17.683594)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <rect\n id=\"rect\"\n x=\"16.033691\"\n y=\"16.271484\"\n rx=\"0.995190\"\n width=\"6.002943\"\n height=\"1.990380\"\n transform=\"rotate(-139.147 16.033691 16.271484)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <path\n id=\"path\"\n d=\"M20.09 25.48L9.89 25.5C9.47 25.5 9.05 25.45 8.64 25.37C8.23 25.29 7.83 25.17 7.44 25C7.05 24.84 6.68 24.64 6.33 24.41C5.98 24.18 5.66 23.91 5.36 23.61C5.07 23.31 4.8 22.99 4.57 22.64C4.34 22.29 4.14 21.92 3.98 21.53C3.82 21.14 3.69 20.74 3.61 20.32C3.53 19.91 3.49 19.49 3.49 19.07L3.49 10.92C3.49 10.5 3.53 10.08 3.61 9.67C3.69 9.25 3.82 8.85 3.98 8.46C4.14 8.07 4.34 7.7 4.57 7.35C4.8 7 5.07 6.68 5.36 6.38C5.66 6.08 5.98 5.81 6.33 5.58C6.68 5.35 7.05 5.15 7.44 4.99C7.83 4.82 8.23 4.7 8.64 4.62C9.05 4.54 9.47 4.5 9.89 4.5L20.09 4.48C20.51 4.48 20.93 4.52 21.34 4.6C21.75 4.69 22.15 4.81 22.54 4.97C22.93 5.13 23.3 5.33 23.65 5.57C24 5.8 24.32 6.06 24.62 6.36C24.92 6.66 25.18 6.98 25.41 7.33C25.65 7.69 25.84 8.06 26.01 8.45C26.17 8.84 26.29 9.24 26.37 9.65C26.45 10.06 26.49 10.48 26.5 10.91L26.5 19.06C26.49 19.48 26.45 19.89 26.37 20.31C26.29 20.72 26.17 21.12 26.01 21.51C25.84 21.9 25.65 22.27 25.41 22.62C25.18 22.97 24.92 23.3 24.62 23.6C24.32 23.89 24 24.16 23.65 24.39C23.3 24.63 22.93 24.83 22.54 24.99C22.15 25.15 21.75 25.27 21.34 25.35C20.93 25.44 20.51 25.48 20.09 25.48ZM9.89 6.59C9.61 6.59 9.32 6.62 9.05 6.67C8.77 6.73 8.5 6.81 8.24 6.92C7.98 7.03 7.73 7.16 7.49 7.32C7.26 7.48 7.04 7.66 6.84 7.86C6.64 8.06 6.46 8.28 6.3 8.51C6.14 8.75 6.01 9 5.9 9.26C5.79 9.52 5.71 9.8 5.66 10.07C5.6 10.35 5.57 10.63 5.57 10.92L5.57 19.07C5.57 19.36 5.6 19.64 5.66 19.92C5.71 20.19 5.79 20.47 5.9 20.73C6.01 20.99 6.14 21.24 6.3 21.48C6.46 21.71 6.64 21.93 6.84 22.13C7.04 22.33 7.26 22.51 7.49 22.67C7.73 22.83 7.98 22.96 8.24 23.07C8.5 23.18 8.77 23.26 9.05 23.32C9.32 23.37 9.61 23.4 9.89 23.4L20.09 23.39C20.38 23.39 20.66 23.36 20.94 23.3C21.21 23.25 21.48 23.17 21.75 23.06C22.01 22.95 22.26 22.81 22.49 22.66C22.73 22.5 22.95 22.32 23.15 22.12C23.35 21.91 23.52 21.7 23.68 21.46C23.84 21.22 23.97 20.98 24.08 20.71C24.19 20.45 24.27 20.18 24.33 19.9C24.38 19.62 24.41 19.34 24.41 19.06L24.41 10.91C24.41 10.62 24.38 10.34 24.33 10.06C24.27 9.78 24.19 9.51 24.08 9.25C23.97 8.98 23.84 8.74 23.68 8.5C23.52 8.26 23.35 8.04 23.15 7.84C22.95 7.64 22.73 7.46 22.49 7.3C22.26 7.15 22.01 7.01 21.75 6.9C21.48 6.79 21.21 6.71 20.94 6.66C20.66 6.6 20.38 6.57 20.09 6.57L9.89 6.59Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"nonzero\"\n ></path>\n <path\n id=\"rect\"\n d=\"M8.49 5.5L10.53 5.5L10.59 24.41L8.54 24.41L8.49 5.5Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"evenodd\"\n ></path>\n </g>\n }\n </svg>\n </ax-icon>\n</ax-button>\n\n<div class=\"axp-root-header__main ax-h-full ax-flex ax-min-w-0 ax-flex-1 ax-items-center ax-gap-2\">\n @if (tabNavigation.isTabBarVisible()) {\n <axp-route-tab-bar class=\"axp-route-tab-bar--in-header ax-min-w-0 ax-flex-1 ax-self-stretch\"></axp-route-tab-bar>\n }\n <div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\" [class.ax-flex-1]=\"!tabNavigation.isTabBarVisible()\">\n <axp-component-slot name=\"root-header-start\"></axp-component-slot>\n </div>\n</div>\n\n<div class=\"ax-flex ax-shrink-0 ax-items-center ax-gap-1\">\n <axp-component-slot name=\"root-header-end\"></axp-component-slot>\n</div>\n", dependencies: [{ kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i1$5.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: AXPRouteTabBarComponent, selector: "axp-route-tab-bar" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3126
+ }
3127
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutHeaderComponent, decorators: [{
3128
+ type: Component,
3129
+ args: [{ selector: 'axp-dashboard-admin-header', encapsulation: ViewEncapsulation.None, host: {
3130
+ class: 'ax-w-full ax-flex ax-min-w-0 ax-items-center ax-gap-2 ax-bg-primary-surface ax-text-primary-on-surface ax-h-14 ax-px-2 ax-light',
3131
+ }, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXPComponentSlotModule, AXButtonModule, AXDecoratorModule, AXPRouteTabBarComponent], template: "<ax-button id=\"axp-menu-toggle\" (onClick)=\"toggleSideMenu()\" color=\"primary\">\n <ax-icon class=\"rtl:ax-rotate-180\">\n <svg width=\"28\" height=\"28\" viewBox=\"0 0 30 30\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n @if (store.isSideMenuOpen()) {\n <defs>\n <clipPath id=\"clip1382_20239\">\n <rect width=\"30.000000\" height=\"30.000000\" fill=\"white\" fill-opacity=\"0\"></rect>\n </clipPath>\n </defs>\n <rect width=\"30.000000\" height=\"30.000000\" fill=\"#FFFFFF\" fill-opacity=\"0\"></rect>\n <g clip-path=\"url(#clip1382_20239)\">\n <rect\n id=\"rect\"\n x=\"17.420410\"\n y=\"12.316406\"\n rx=\"1.000947\"\n width=\"5.995172\"\n height=\"2.001895\"\n transform=\"rotate(137.159 17.420410 12.316406)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <rect\n id=\"rect\"\n x=\"12.959473\"\n y=\"13.728516\"\n rx=\"0.995190\"\n width=\"6.002943\"\n height=\"1.990380\"\n transform=\"rotate(40.853 12.959473 13.728516)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <path\n id=\"path\"\n d=\"M20.1 25.5L9.9 25.51C9.48 25.51 9.06 25.47 8.65 25.39C8.24 25.3 7.84 25.18 7.45 25.02C7.06 24.86 6.69 24.66 6.34 24.43C5.99 24.19 5.67 23.92 5.37 23.63C5.07 23.33 4.81 23 4.58 22.65C4.34 22.3 4.15 21.93 3.98 21.54C3.82 21.15 3.7 20.75 3.62 20.34C3.54 19.93 3.5 19.51 3.5 19.09L3.5 10.93C3.5 10.51 3.54 10.1 3.62 9.68C3.7 9.27 3.82 8.87 3.98 8.48C4.15 8.09 4.34 7.72 4.58 7.37C4.81 7.02 5.07 6.69 5.37 6.39C5.67 6.1 5.99 5.83 6.34 5.6C6.69 5.36 7.06 5.16 7.45 5C7.84 4.84 8.24 4.72 8.65 4.64C9.06 4.55 9.48 4.51 9.9 4.51L20.1 4.5C20.52 4.5 20.94 4.54 21.35 4.62C21.76 4.7 22.16 4.83 22.55 4.99C22.94 5.15 23.31 5.35 23.66 5.58C24 5.82 24.33 6.08 24.62 6.38C24.92 6.68 25.19 7 25.42 7.35C25.65 7.7 25.85 8.07 26.01 8.46C26.17 8.85 26.3 9.25 26.38 9.67C26.46 10.08 26.5 10.5 26.5 10.92L26.5 19.07C26.5 19.5 26.46 19.91 26.38 20.32C26.3 20.74 26.17 21.14 26.01 21.53C25.85 21.92 25.65 22.29 25.42 22.64C25.19 22.99 24.92 23.31 24.62 23.61C24.33 23.91 24 24.17 23.66 24.41C23.31 24.64 22.94 24.84 22.55 25C22.16 25.16 21.76 25.29 21.35 25.37C20.94 25.45 20.52 25.5 20.1 25.5ZM9.9 6.6C9.61 6.6 9.33 6.63 9.05 6.69C8.78 6.74 8.51 6.82 8.24 6.93C7.98 7.04 7.73 7.18 7.5 7.33C7.26 7.49 7.04 7.67 6.84 7.87C6.64 8.07 6.46 8.29 6.31 8.53C6.15 8.77 6.02 9.01 5.91 9.28C5.8 9.54 5.72 9.81 5.66 10.09C5.61 10.37 5.58 10.65 5.58 10.93L5.58 19.09C5.58 19.37 5.61 19.65 5.66 19.93C5.72 20.21 5.8 20.48 5.91 20.74C6.02 21.01 6.15 21.26 6.31 21.49C6.46 21.73 6.64 21.95 6.84 22.15C7.04 22.35 7.26 22.53 7.5 22.69C7.73 22.85 7.98 22.98 8.24 23.09C8.51 23.2 8.78 23.28 9.05 23.33C9.33 23.39 9.61 23.42 9.9 23.42L20.1 23.41C20.38 23.41 20.67 23.37 20.94 23.32C21.22 23.26 21.49 23.18 21.75 23.07C22.01 22.96 22.26 22.83 22.5 22.67C22.73 22.51 22.95 22.33 23.15 22.13C23.35 21.93 23.53 21.71 23.69 21.48C23.85 21.24 23.98 20.99 24.09 20.73C24.2 20.47 24.28 20.2 24.33 19.92C24.39 19.64 24.42 19.36 24.42 19.07L24.42 10.92C24.42 10.64 24.39 10.35 24.33 10.07C24.28 9.79 24.2 9.52 24.09 9.26C23.98 9 23.85 8.75 23.69 8.51C23.53 8.28 23.35 8.06 23.15 7.86C22.95 7.66 22.73 7.48 22.5 7.32C22.26 7.16 22.01 7.03 21.75 6.92C21.49 6.81 21.22 6.73 20.94 6.67C20.67 6.62 20.38 6.59 20.1 6.59L9.9 6.6Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"nonzero\"\n ></path>\n <path\n id=\"rect\"\n d=\"M8.5 5.51L10.54 5.51L10.6 24.43L8.55 24.43L8.5 5.51Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"evenodd\"\n ></path>\n </g>\n } @else {\n <defs>\n <clipPath id=\"clip1381_20236\">\n <rect id=\"\u6253\u5F00\u8FB9\u680F0730\" width=\"30.000000\" height=\"30.000000\" fill=\"white\" fill-opacity=\"0\"></rect>\n </clipPath>\n </defs>\n <rect id=\"\u6253\u5F00\u8FB9\u680F0730\" width=\"30.000000\" height=\"30.000000\" fill=\"#FFFFFF\" fill-opacity=\"0\"></rect>\n <g clip-path=\"url(#clip1381_20236)\">\n <rect\n id=\"rect\"\n x=\"11.572754\"\n y=\"17.683594\"\n rx=\"1.000947\"\n width=\"5.995172\"\n height=\"2.001895\"\n transform=\"rotate(-42.841 11.572754 17.683594)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <rect\n id=\"rect\"\n x=\"16.033691\"\n y=\"16.271484\"\n rx=\"0.995190\"\n width=\"6.002943\"\n height=\"1.990380\"\n transform=\"rotate(-139.147 16.033691 16.271484)\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n ></rect>\n <path\n id=\"path\"\n d=\"M20.09 25.48L9.89 25.5C9.47 25.5 9.05 25.45 8.64 25.37C8.23 25.29 7.83 25.17 7.44 25C7.05 24.84 6.68 24.64 6.33 24.41C5.98 24.18 5.66 23.91 5.36 23.61C5.07 23.31 4.8 22.99 4.57 22.64C4.34 22.29 4.14 21.92 3.98 21.53C3.82 21.14 3.69 20.74 3.61 20.32C3.53 19.91 3.49 19.49 3.49 19.07L3.49 10.92C3.49 10.5 3.53 10.08 3.61 9.67C3.69 9.25 3.82 8.85 3.98 8.46C4.14 8.07 4.34 7.7 4.57 7.35C4.8 7 5.07 6.68 5.36 6.38C5.66 6.08 5.98 5.81 6.33 5.58C6.68 5.35 7.05 5.15 7.44 4.99C7.83 4.82 8.23 4.7 8.64 4.62C9.05 4.54 9.47 4.5 9.89 4.5L20.09 4.48C20.51 4.48 20.93 4.52 21.34 4.6C21.75 4.69 22.15 4.81 22.54 4.97C22.93 5.13 23.3 5.33 23.65 5.57C24 5.8 24.32 6.06 24.62 6.36C24.92 6.66 25.18 6.98 25.41 7.33C25.65 7.69 25.84 8.06 26.01 8.45C26.17 8.84 26.29 9.24 26.37 9.65C26.45 10.06 26.49 10.48 26.5 10.91L26.5 19.06C26.49 19.48 26.45 19.89 26.37 20.31C26.29 20.72 26.17 21.12 26.01 21.51C25.84 21.9 25.65 22.27 25.41 22.62C25.18 22.97 24.92 23.3 24.62 23.6C24.32 23.89 24 24.16 23.65 24.39C23.3 24.63 22.93 24.83 22.54 24.99C22.15 25.15 21.75 25.27 21.34 25.35C20.93 25.44 20.51 25.48 20.09 25.48ZM9.89 6.59C9.61 6.59 9.32 6.62 9.05 6.67C8.77 6.73 8.5 6.81 8.24 6.92C7.98 7.03 7.73 7.16 7.49 7.32C7.26 7.48 7.04 7.66 6.84 7.86C6.64 8.06 6.46 8.28 6.3 8.51C6.14 8.75 6.01 9 5.9 9.26C5.79 9.52 5.71 9.8 5.66 10.07C5.6 10.35 5.57 10.63 5.57 10.92L5.57 19.07C5.57 19.36 5.6 19.64 5.66 19.92C5.71 20.19 5.79 20.47 5.9 20.73C6.01 20.99 6.14 21.24 6.3 21.48C6.46 21.71 6.64 21.93 6.84 22.13C7.04 22.33 7.26 22.51 7.49 22.67C7.73 22.83 7.98 22.96 8.24 23.07C8.5 23.18 8.77 23.26 9.05 23.32C9.32 23.37 9.61 23.4 9.89 23.4L20.09 23.39C20.38 23.39 20.66 23.36 20.94 23.3C21.21 23.25 21.48 23.17 21.75 23.06C22.01 22.95 22.26 22.81 22.49 22.66C22.73 22.5 22.95 22.32 23.15 22.12C23.35 21.91 23.52 21.7 23.68 21.46C23.84 21.22 23.97 20.98 24.08 20.71C24.19 20.45 24.27 20.18 24.33 19.9C24.38 19.62 24.41 19.34 24.41 19.06L24.41 10.91C24.41 10.62 24.38 10.34 24.33 10.06C24.27 9.78 24.19 9.51 24.08 9.25C23.97 8.98 23.84 8.74 23.68 8.5C23.52 8.26 23.35 8.04 23.15 7.84C22.95 7.64 22.73 7.46 22.49 7.3C22.26 7.15 22.01 7.01 21.75 6.9C21.48 6.79 21.21 6.71 20.94 6.66C20.66 6.6 20.38 6.57 20.09 6.57L9.89 6.59Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"nonzero\"\n ></path>\n <path\n id=\"rect\"\n d=\"M8.49 5.5L10.53 5.5L10.59 24.41L8.54 24.41L8.49 5.5Z\"\n fill=\"currentColor\"\n fill-opacity=\"1.000000\"\n fill-rule=\"evenodd\"\n ></path>\n </g>\n }\n </svg>\n </ax-icon>\n</ax-button>\n\n<div class=\"axp-root-header__main ax-h-full ax-flex ax-min-w-0 ax-flex-1 ax-items-center ax-gap-2\">\n @if (tabNavigation.isTabBarVisible()) {\n <axp-route-tab-bar class=\"axp-route-tab-bar--in-header ax-min-w-0 ax-flex-1 ax-self-stretch\"></axp-route-tab-bar>\n }\n <div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\" [class.ax-flex-1]=\"!tabNavigation.isTabBarVisible()\">\n <axp-component-slot name=\"root-header-start\"></axp-component-slot>\n </div>\n</div>\n\n<div class=\"ax-flex ax-shrink-0 ax-items-center ax-gap-1\">\n <axp-component-slot name=\"root-header-end\"></axp-component-slot>\n</div>\n" }]
3132
+ }], ctorParameters: () => [] });
3133
+
3134
+ class AXPRootLayoutMenuComponent {
3135
+ constructor() {
3136
+ //#region ---- Services & Dependencies ----
3137
+ this.menuStore = inject(AXPMenuService);
3138
+ this.router = inject(Router);
3139
+ this.badgeService = inject(AXPTaskBadgeService);
3140
+ this.settingsService = inject(AXPSettingsService);
3141
+ this.visibilityService = inject(AXPMenuVisibilityService);
3142
+ this.themeStore = inject(AXPLayoutThemeService);
3143
+ //#endregion
3144
+ //#region ---- Component State ----
3145
+ this.badgeVisible = signal(true, ...(ngDevMode ? [{ debugName: "badgeVisible" }] : /* istanbul ignore next */ []));
3146
+ this.menuMode = this.themeStore.effectiveMenuVerticalMode;
3147
+ }
3148
+ //#endregion
3149
+ async ngOnInit() {
3150
+ this.badgeVisible.set(await this.settingsService.get(AXPThemeLayoutSetting.MenuBadgeVisible));
3151
+ this.settingsService.onChanged.subscribe((setting) => {
3152
+ if (setting.keys.includes(AXPThemeLayoutSetting.MenuBadgeVisible)) {
3153
+ this.badgeVisible.set(setting.values[AXPThemeLayoutSetting.MenuBadgeVisible]);
3154
+ }
3155
+ });
3156
+ }
3157
+ onMenuItemClick(item) {
3158
+ if (item.children?.length) {
3159
+ return;
3160
+ }
3161
+ this.menuStore.executeCommand(item);
3162
+ }
3163
+ /**
3164
+ * Determines whether a menu item should render as active.
3165
+ * Do not bind `routerLink` on `ax-side-menu-item`: that component ignores the `active` input
3166
+ * and only uses `RouterLinkActive`, which cannot match entity detail routes to list menu paths.
3167
+ */
3168
+ /**
3169
+ * In compact mode, keep all branches collapsed so route-driven selection does not
3170
+ * auto-open flyout submenus when switching internal tabs.
3171
+ */
3172
+ isMenuItemCollapsed(item) {
3173
+ if (this.menuMode() === AXPMenuVerticalMode.Compact) {
3174
+ return true;
3175
+ }
3176
+ return !this.menuStore.isItemOpen(item);
3177
+ }
3178
+ isMenuItemActive(item) {
3179
+ const selected = this.menuStore.selectedMenuItem().item;
3180
+ if (selected && this.isSameMenuItem(selected, item)) {
3181
+ return true;
3182
+ }
3183
+ const routePath = this.getMenuItemRoutePath(item);
3184
+ if (!routePath) {
3185
+ return false;
3186
+ }
3187
+ return axpMenuPathsMatch(routePath, this.router.url).isMatch;
3188
+ }
3189
+ isSameMenuItem(left, right) {
3190
+ if (left === right) {
3191
+ return true;
3192
+ }
3193
+ return !!left.name && left.name === right.name;
3194
+ }
3195
+ getMenuItemRoutePath(item) {
3196
+ const navigation = this.extractRouterNavigation(item);
3197
+ if (!navigation?.path) {
3198
+ return null;
3199
+ }
3200
+ return this.normalizeRouterPath(navigation.path);
3201
+ }
3202
+ sort(items) {
3203
+ return sortBy(items, [(c) => c.priority ?? 0, (c) => c.text]);
3204
+ }
3205
+ getMenuBadge(item) {
3206
+ return AXPMenuBadgeHelper.getTotalCount(item, this.badgeService);
3207
+ }
3208
+ //#region ---- Menu Visibility Helpers ----
3209
+ /**
3210
+ * Checks if a menu item should be visible based on permissions and features.
3211
+ */
3212
+ isItemVisible(item) {
3213
+ return this.visibilityService.isItemVisible(item);
3214
+ }
3215
+ /**
3216
+ * Determines if a menu item should be rendered.
3217
+ * A menu item should be rendered if:
3218
+ * 1. It has a routerLink, OR
3219
+ * 2. It has visible children, OR
3220
+ * 3. It has a command (for non-router navigation)
3221
+ */
3222
+ shouldRenderMenuItem(item) {
3223
+ return this.visibilityService.shouldRenderMenuItem(item, (item) => this.getRouterLink(item));
3224
+ }
3225
+ /**
3226
+ * Checks if an item takes up visual space in the menu (group title or rendered menu item).
3227
+ */
3228
+ isItemRendered(item) {
3229
+ if (item.type === 'break') {
3230
+ return false;
3231
+ }
3232
+ if (item.type === 'group') {
3233
+ return this.isItemVisible(item);
3234
+ }
3235
+ return this.shouldRenderMenuItem(item);
3236
+ }
3237
+ /**
3238
+ * Determines if a divider should be shown. A divider is only shown when there is
3239
+ * visible content on both sides; if before or after is blank, the divider is hidden.
3240
+ */
3241
+ shouldShowDivider(item, items, index) {
3242
+ if (item.type !== 'break' || !items || index < 0) {
3243
+ return false;
3244
+ }
3245
+ const hasVisibleBefore = items.slice(0, index).some((i) => this.isItemRendered(i));
3246
+ const hasVisibleAfter = items.slice(index + 1).some((i) => this.isItemRendered(i));
3247
+ return hasVisibleBefore && hasVisibleAfter;
3248
+ }
3249
+ //#endregion
3250
+ //#region ---- Menu Navigation Helpers ----
3251
+ /**
3252
+ * Builds a UrlTree that can be consumed by the routerLink directive when the menu item targets
3253
+ * an internal route. When the item does not represent an internal route, null is returned so the
3254
+ * command execution pipeline remains responsible for navigation.
3255
+ */
3256
+ getRouterLink(item) {
3257
+ const routePath = this.getMenuItemRoutePath(item);
3258
+ if (!routePath) {
3259
+ return null;
3260
+ }
3261
+ const navigation = this.extractRouterNavigation(item);
3262
+ if (!navigation || !this.isRouterLinkCompatible(navigation)) {
3263
+ return null;
3264
+ }
3265
+ try {
3266
+ return this.router.parseUrl(routePath);
3267
+ }
3268
+ catch (error) {
3269
+ console.warn('Failed to create router link for menu item.', { item, error });
3270
+ return null;
3271
+ }
3272
+ }
3273
+ /**
3274
+ * Extracts internal navigation data regardless of whether it is provided via the legacy `path`
3275
+ * property, a direct navigate command, or the execution command wrapper.
3276
+ */
3277
+ extractRouterNavigation(item) {
3278
+ if (item.path) {
3279
+ return { path: item.path };
3280
+ }
3281
+ const command = item.command;
3282
+ if (!command) {
3283
+ return null;
3284
+ }
3285
+ const navigateCommand = command;
3286
+ if (navigateCommand?.type === 'router' && typeof navigateCommand.options?.path === 'string') {
3287
+ return {
3288
+ path: navigateCommand.options.path,
3289
+ extras: navigateCommand.options.extras,
3290
+ };
3291
+ }
3292
+ const executeCommand = command;
3293
+ if (executeCommand?.options && typeof executeCommand.options === 'object') {
3294
+ const commandOptions = executeCommand.options;
3295
+ if (commandOptions?.type === 'router') {
3296
+ const routerOptions = commandOptions.options ?? commandOptions;
3297
+ if (typeof routerOptions?.path === 'string') {
3298
+ return {
3299
+ path: routerOptions.path,
3300
+ extras: routerOptions.extras,
3301
+ };
3302
+ }
3303
+ }
3304
+ if (typeof commandOptions?.path === 'string') {
3305
+ return { path: commandOptions.path };
3306
+ }
3307
+ }
3308
+ return null;
3309
+ }
3310
+ /**
3311
+ * Determines whether routerLink can safely be used based on the target path and navigation
3312
+ * extras. If the navigation requires additional runtime behaviour (e.g., opening in a new tab),
3313
+ * we fallback to the imperative command pipeline.
3314
+ */
3315
+ isRouterLinkCompatible(navigation) {
3316
+ if (!navigation.path) {
3317
+ return false;
3318
+ }
3319
+ if (this.isExternalNavigation(navigation.path)) {
3320
+ return false;
3321
+ }
3322
+ const extras = navigation.extras;
3323
+ if (!extras) {
3324
+ return true;
3325
+ }
3326
+ if (extras.target === 'blank') {
3327
+ return false;
3328
+ }
3329
+ const unsupportedKeys = ['skipLocationChange', 'replaceUrl', 'state'];
3330
+ return !unsupportedKeys.some((key) => extras[key] !== undefined);
3331
+ }
3332
+ /**
3333
+ * Ensures router links include a leading slash while preserving any query parameters or
3334
+ * fragments present in the original path.
3335
+ */
3336
+ normalizeRouterPath(path) {
3337
+ const trimmed = path.trim();
3338
+ if (!trimmed.length) {
3339
+ return trimmed;
3340
+ }
3341
+ if (trimmed.startsWith('/') || trimmed.startsWith('?') || trimmed.startsWith('#')) {
3342
+ return trimmed;
3343
+ }
3344
+ return `/${trimmed}`;
3345
+ }
3346
+ isExternalNavigation(path) {
3347
+ const lowerPath = path.trim().toLowerCase();
3348
+ return lowerPath.startsWith('http://') || lowerPath.startsWith('https://');
3349
+ }
3350
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3351
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRootLayoutMenuComponent, isStandalone: true, selector: "axp-side-menu", ngImport: i0, template: "<ax-side-menu look=\"pills\" [mode]=\"menuMode()\">\n @let sortedItems = sort(menuStore.items());\n @for (item of sortedItems; track item; let i = $index) {\n <ng-container\n [ngTemplateOutlet]=\"recursiveMenu\"\n [ngTemplateOutletContext]=\"{ item: item, items: sortedItems, index: i }\"\n >\n </ng-container>\n }\n</ax-side-menu>\n<ng-template #recursiveMenu let-item=\"item\" let-items=\"items\" let-index=\"index\">\n @if (item.type == 'group' && menuMode() === 'full') {\n @if (isItemVisible(item)) {\n <ax-title>{{ item.text | translate | async }}</ax-title>\n }\n } @else if (item.type == 'break') {\n @if (shouldShowDivider(item, items, index)) {\n <ax-divider></ax-divider>\n }\n } @else {\n @if (shouldRenderMenuItem(item)) {\n <ax-side-menu-item\n [attr.id]=\"'axp-menu-item-' + (item.name || item.text)\"\n (onClick)=\"onMenuItemClick(item)\"\n [active]=\"isMenuItemActive(item)\"\n [isCollapsed]=\"isMenuItemCollapsed(item)\"\n >\n <ax-prefix>\n <ax-icon [class]=\"item.icon\" class=\"fa-fw\"></ax-icon>\n </ax-prefix>\n <span>{{ item.text | translate | async }}</span>\n @if (item.children?.length) {\n <ng-container>\n @let sortedChildren = sort(item.children);\n @for (child of sortedChildren; track child; let childIndex = $index) {\n <ng-container\n [ngTemplateOutlet]=\"recursiveMenu\"\n [ngTemplateOutletContext]=\"{ item: child, items: sortedChildren, index: childIndex }\"\n >\n </ng-container>\n }\n </ng-container>\n }\n <ax-suffix>\n @if (badgeVisible()) {\n <ax-badge [axp-task-badge]=\"getMenuBadge(item)()\" color=\"secondary\"></ax-badge>\n }\n </ax-suffix>\n </ax-side-menu-item>\n }\n }\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: AXSideMenuModule }, { kind: "component", type: i2$4.AXSideMenuComponent, selector: "ax-side-menu", inputs: ["items", "look", "location", "mode"], outputs: ["itemsChange"] }, { kind: "component", type: i2$4.AXSideMenuItemComponent, selector: "ax-side-menu-item", inputs: ["disabled", "text", "active", "isLoading", "isCollapsed", "toggleOnClick", "href", "routerLink", "routerLinkActive", "routerLinkActiveOptions", "target"], outputs: ["textChange", "activeChange", "isLoadingChange", "isCollapsedChange", "onClick"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: AXPTaskBadgeDirective, selector: "[axp-task-badge]", inputs: ["badgeKey", "axp-task-badge"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3352
+ }
3353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutMenuComponent, decorators: [{
3354
+ type: Component,
3355
+ args: [{ selector: 'axp-side-menu', encapsulation: ViewEncapsulation.None, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
3356
+ CommonModule,
3357
+ AXSideMenuModule,
3358
+ AXTranslationModule,
3359
+ AXPTaskBadgeDirective,
3360
+ AXDecoratorModule,
3361
+ AXBadgeModule,
3362
+ ], template: "<ax-side-menu look=\"pills\" [mode]=\"menuMode()\">\n @let sortedItems = sort(menuStore.items());\n @for (item of sortedItems; track item; let i = $index) {\n <ng-container\n [ngTemplateOutlet]=\"recursiveMenu\"\n [ngTemplateOutletContext]=\"{ item: item, items: sortedItems, index: i }\"\n >\n </ng-container>\n }\n</ax-side-menu>\n<ng-template #recursiveMenu let-item=\"item\" let-items=\"items\" let-index=\"index\">\n @if (item.type == 'group' && menuMode() === 'full') {\n @if (isItemVisible(item)) {\n <ax-title>{{ item.text | translate | async }}</ax-title>\n }\n } @else if (item.type == 'break') {\n @if (shouldShowDivider(item, items, index)) {\n <ax-divider></ax-divider>\n }\n } @else {\n @if (shouldRenderMenuItem(item)) {\n <ax-side-menu-item\n [attr.id]=\"'axp-menu-item-' + (item.name || item.text)\"\n (onClick)=\"onMenuItemClick(item)\"\n [active]=\"isMenuItemActive(item)\"\n [isCollapsed]=\"isMenuItemCollapsed(item)\"\n >\n <ax-prefix>\n <ax-icon [class]=\"item.icon\" class=\"fa-fw\"></ax-icon>\n </ax-prefix>\n <span>{{ item.text | translate | async }}</span>\n @if (item.children?.length) {\n <ng-container>\n @let sortedChildren = sort(item.children);\n @for (child of sortedChildren; track child; let childIndex = $index) {\n <ng-container\n [ngTemplateOutlet]=\"recursiveMenu\"\n [ngTemplateOutletContext]=\"{ item: child, items: sortedChildren, index: childIndex }\"\n >\n </ng-container>\n }\n </ng-container>\n }\n <ax-suffix>\n @if (badgeVisible()) {\n <ax-badge [axp-task-badge]=\"getMenuBadge(item)()\" color=\"secondary\"></ax-badge>\n }\n </ax-suffix>\n </ax-side-menu-item>\n }\n }\n</ng-template>\n" }]
3363
+ }] });
3364
+
3365
+ class AXPHorizontalMenuComponent {
3366
+ constructor() {
3367
+ this.platformConfig = inject(AXP_PLATFORM_CONFIG_TOKEN);
3368
+ this.menuStore = inject(AXPMenuService);
3369
+ this.router = inject(Router);
3370
+ this.layoutService = inject(AXPLayoutThemeService);
3371
+ this.badgeService = inject(AXPTaskBadgeService);
3372
+ this.settingsService = inject(AXPSettingsService);
3373
+ this.visibilityService = inject(AXPMenuVisibilityService);
3374
+ this.badgeVisible = signal(true, ...(ngDevMode ? [{ debugName: "badgeVisible" }] : /* istanbul ignore next */ []));
3375
+ }
3376
+ async ngOnInit() {
3377
+ this.badgeVisible.set(await this.settingsService.get(AXPThemeLayoutSetting.MenuBadgeVisible));
3378
+ //subscribe to changes
3379
+ this.settingsService.onChanged.subscribe(setting => {
3380
+ if (setting.keys.includes(AXPThemeLayoutSetting.MenuBadgeVisible)) {
3381
+ this.badgeVisible.set(setting.values[AXPThemeLayoutSetting.MenuBadgeVisible]);
3382
+ }
3383
+ });
3384
+ }
3385
+ onMenuItemClick(item) {
3386
+ if (!item.children?.length) {
3387
+ this.menuStore.executeCommand(item);
3388
+ }
3389
+ }
3390
+ sort(items) {
3391
+ return sortBy$1(items, [c => c.priority ?? 0, c => c.text]);
3392
+ }
3393
+ getMenuBadge(item) {
3394
+ return AXPMenuBadgeHelper.getTotalCount(item, this.badgeService);
3395
+ }
3396
+ //#region ---- Menu Visibility Helpers ----
3397
+ /**
3398
+ * Checks if a menu item should be visible based on permissions and features.
3399
+ */
3400
+ isItemVisible(item) {
3401
+ return this.visibilityService.isItemVisible(item);
3402
+ }
3403
+ /**
3404
+ * Determines if a menu item should be rendered.
3405
+ * A menu item should be rendered if:
3406
+ * 1. It has a routerLink, OR
3407
+ * 2. It has visible children, OR
3408
+ * 3. It has a command (for non-router navigation)
3409
+ */
3410
+ shouldRenderMenuItem(item) {
3411
+ return this.visibilityService.shouldRenderMenuItem(item, (item) => this.getRouterLink(item));
3412
+ }
3413
+ /**
3414
+ * Checks if an item takes up visual space in the menu (group title or rendered menu item).
3415
+ */
3416
+ isItemRendered(item) {
3417
+ if (item.type === 'break') {
3418
+ return false;
3419
+ }
3420
+ if (item.type === 'group') {
3421
+ return this.isItemVisible(item);
3422
+ }
3423
+ return this.shouldRenderMenuItem(item);
3424
+ }
3425
+ /**
3426
+ * Determines if a divider should be shown. A divider is only shown when there is
3427
+ * visible content on both sides; if before or after is blank, the divider is hidden.
3428
+ */
3429
+ shouldShowDivider(item, items, index) {
3430
+ if (item.type !== 'break' || !items || index < 0) {
3431
+ return false;
3432
+ }
3433
+ const hasVisibleBefore = items.slice(0, index).some((i) => this.isItemRendered(i));
3434
+ const hasVisibleAfter = items.slice(index + 1).some((i) => this.isItemRendered(i));
3435
+ return hasVisibleBefore && hasVisibleAfter;
3436
+ }
3437
+ //#endregion
3438
+ //#region ---- Menu Navigation Helpers ----
3439
+ /**
3440
+ * Builds a UrlTree that can be consumed by the routerLink directive when the menu item targets
3441
+ * an internal route. When the item does not represent an internal route, null is returned so the
3442
+ * command execution pipeline remains responsible for navigation.
3443
+ */
3444
+ getRouterLink(item) {
3445
+ const navigation = this.extractRouterNavigation(item);
3446
+ if (!navigation) {
3447
+ return null;
3448
+ }
3449
+ if (!this.isRouterLinkCompatible(navigation)) {
3450
+ return null;
3451
+ }
3452
+ const normalizedPath = this.normalizeRouterPath(navigation.path);
3453
+ try {
3454
+ return this.router.parseUrl(normalizedPath);
3455
+ }
3456
+ catch (error) {
3457
+ console.warn('Failed to create router link for menu item.', { item, error });
3458
+ return null;
3459
+ }
3460
+ }
3461
+ /**
3462
+ * Extracts internal navigation data regardless of whether it is provided via the legacy `path`
3463
+ * property, a direct navigate command, or the execution command wrapper.
3464
+ */
3465
+ extractRouterNavigation(item) {
3466
+ if (item.path) {
3467
+ return { path: item.path };
3468
+ }
3469
+ const command = item.command;
3470
+ if (!command) {
3471
+ return null;
3472
+ }
3473
+ const navigateCommand = command;
3474
+ if (navigateCommand?.type === 'router' && typeof navigateCommand.options?.path === 'string') {
3475
+ return {
3476
+ path: navigateCommand.options.path,
3477
+ extras: navigateCommand.options.extras,
3478
+ };
3479
+ }
3480
+ const executeCommand = command;
3481
+ if (executeCommand?.options && typeof executeCommand.options === 'object') {
3482
+ const commandOptions = executeCommand.options;
3483
+ if (commandOptions?.type === 'router') {
3484
+ const routerOptions = commandOptions.options ?? commandOptions;
3485
+ if (typeof routerOptions?.path === 'string') {
3486
+ return {
3487
+ path: routerOptions.path,
3488
+ extras: routerOptions.extras,
3489
+ };
3490
+ }
3491
+ }
3492
+ if (typeof commandOptions?.path === 'string') {
3493
+ return { path: commandOptions.path };
3494
+ }
3495
+ }
3496
+ return null;
3497
+ }
3498
+ /**
3499
+ * Determines whether routerLink can safely be used based on the target path and navigation
3500
+ * extras. If the navigation requires additional runtime behaviour (e.g., opening in a new tab),
3501
+ * we fallback to the imperative command pipeline.
3502
+ */
3503
+ isRouterLinkCompatible(navigation) {
3504
+ if (!navigation.path) {
3505
+ return false;
3506
+ }
3507
+ if (this.isExternalNavigation(navigation.path)) {
3508
+ return false;
3509
+ }
3510
+ const extras = navigation.extras;
3511
+ if (!extras) {
3512
+ return true;
3513
+ }
3514
+ if (extras.target === 'blank') {
3515
+ return false;
3516
+ }
3517
+ const unsupportedKeys = ['skipLocationChange', 'replaceUrl', 'state'];
3518
+ return !unsupportedKeys.some((key) => extras[key] !== undefined);
3519
+ }
3520
+ /**
3521
+ * Ensures router links include a leading slash while preserving any query parameters or
3522
+ * fragments present in the original path.
3523
+ */
3524
+ normalizeRouterPath(path) {
3525
+ const trimmed = path.trim();
3526
+ if (!trimmed.length) {
3527
+ return trimmed;
3528
+ }
3529
+ if (trimmed.startsWith('/') || trimmed.startsWith('?') || trimmed.startsWith('#')) {
3530
+ return trimmed;
3531
+ }
3532
+ return `/${trimmed}`;
3533
+ }
3534
+ isExternalNavigation(path) {
3535
+ const lowerPath = path.trim().toLowerCase();
3536
+ return lowerPath.startsWith('http://') || lowerPath.startsWith('https://');
3537
+ }
3538
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHorizontalMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3539
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPHorizontalMenuComponent, isStandalone: true, selector: "axp-horizontal-menu", host: { classAttribute: "ax-light" }, ngImport: i0, template: "<div\n class=\"ax-w-full ax-flex ax-items-center ax-justify-between ax-gap-1 ax-bg-primary-surface ax-text-primary-on-surface ax-h-14 ax-px-4\"\n>\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <axp-logo\n [source]=\"platformConfig.logo?.full?.dark\"\n [attr.alt]=\"platformConfig.title\"\n class=\"ax-mx-auto ax-text-2xl\"\n ></axp-logo>\n </div>\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <axp-component-slot name=\"root-header-start\"></axp-component-slot>\n <axp-component-slot name=\"root-header-end\"></axp-component-slot>\n </div>\n</div>\n\n<div class=\"__menu-bar\">\n <ax-menu [orientation]=\"'horizontal'\" [class.ax-dark]=\"layoutService.isDarkMode()\" [hasArrow]=\"true\">\n @let sortedItems = sort(menuStore.items());\n @for (item of sortedItems; track item; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"recursiveMenu\" [ngTemplateOutletContext]=\"{ item: item, items: sortedItems, index: i }\"> </ng-container>\n }\n <!-- Item Templates -->\n <ng-template #recursiveMenu let-item=\"item\" let-items=\"items\" let-index=\"index\">\n @if (item.type == 'group') {\n @if (isItemVisible(item)) {\n <ax-title>{{ item.text | translate | async }}</ax-title>\n }\n } @else if (item.type == 'break') {\n @if (shouldShowDivider(item, items, index)) {\n <ax-divider></ax-divider>\n }\n } @else {\n @if (shouldRenderMenuItem(item)) {\n <ax-menu-item (onClick)=\"onMenuItemClick(item)\">\n @if (!item.meta?.isRoot) {\n <ax-prefix>\n <ax-icon [class]=\"item.icon\" class=\"fa-fw\"></ax-icon>\n </ax-prefix>\n }\n <ax-text> {{ item.text | translate | async }}</ax-text>\n @if (item.children?.length) {\n <ng-container>\n @let sortedChildren = sort(item.children);\n @for (child of sortedChildren; track child; let childIndex = $index) {\n <ng-container [ngTemplateOutlet]=\"recursiveMenu\" [ngTemplateOutletContext]=\"{ item: child, items: sortedChildren, index: childIndex }\">\n </ng-container>\n }\n </ng-container>\n }\n <ax-suffix>\n @if (badgeVisible()) {\n <ax-badge [axp-task-badge]=\"getMenuBadge(item)()\" color=\"secondary\"></ax-badge>\n }\n </ax-suffix>\n </ax-menu-item>\n }\n }\n </ng-template>\n </ax-menu>\n</div>\n<!-- <div class=\"__tab-bar\">\n<div class=\"axp-tabs-item axp-state-active\">\n <span class=\"ax-font-medium\">Account</span>\n <i class=\"fa-light fa-times ax-text-sm\"></i>\n</div>\n<div class=\"axp-tabs-item\">\n <span class=\"ax-font-medium\">Projects</span>\n\n</div>\n</div> -->\n", styles: [".__tab-bar{display:flex;height:3rem;width:100%;align-items:flex-end;gap:.5rem;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-darker-surface),var(--tw-bg-opacity, 1));padding-left:1rem;padding-right:1rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-darker-surface),var(--tw-text-opacity, 1))}.__tab-bar .axp-tabs-item{display:flex;height:2.5rem;cursor:pointer;align-items:center;justify-content:space-between;gap:.5rem;border-top-left-radius:.5rem;border-top-right-radius:.5rem;padding-left:1rem;padding-right:1rem;font-size:.875rem;line-height:1.25rem;line-height:1}.__tab-bar .axp-tabs-item:hover,.__tab-bar .axp-tabs-item.axp-state-active{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1))}.__menu-bar{display:flex;width:100%;align-items:flex-end;gap:.5rem;overflow-x:auto;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-dark-surface),var(--tw-bg-opacity, 1));padding:.375rem 1rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-dark-surface),var(--tw-text-opacity, 1))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i1$5.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "component", type: AXPLogoComponent, selector: "axp-logo", inputs: ["source"] }, { kind: "ngmodule", type: AXMenuModule }, { kind: "component", type: i4.AXMenuItemComponent, selector: "ax-menu-item", inputs: ["subItems", "name", "data", "disabled", "color"], outputs: ["onClick"] }, { kind: "component", type: i4.AXMenuComponent, selector: "ax-menu", inputs: ["orientation", "openOn", "closeOn", "items", "hasArrow"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: AXPTaskBadgeDirective, selector: "[axp-task-badge]", inputs: ["badgeKey", "axp-task-badge"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3540
+ }
3541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHorizontalMenuComponent, decorators: [{
3542
+ type: Component,
3543
+ args: [{ selector: 'axp-horizontal-menu', host: { class: 'ax-light' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
3544
+ CommonModule,
3545
+ AXPComponentSlotModule,
3546
+ AXDecoratorModule,
3547
+ AXPLogoComponent,
3548
+ AXMenuModule,
3549
+ AXTranslationModule,
3550
+ AXPTaskBadgeDirective,
3551
+ AXBadgeModule,
3552
+ ], template: "<div\n class=\"ax-w-full ax-flex ax-items-center ax-justify-between ax-gap-1 ax-bg-primary-surface ax-text-primary-on-surface ax-h-14 ax-px-4\"\n>\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <axp-logo\n [source]=\"platformConfig.logo?.full?.dark\"\n [attr.alt]=\"platformConfig.title\"\n class=\"ax-mx-auto ax-text-2xl\"\n ></axp-logo>\n </div>\n <div class=\"ax-flex ax-items-center ax-gap-1\">\n <axp-component-slot name=\"root-header-start\"></axp-component-slot>\n <axp-component-slot name=\"root-header-end\"></axp-component-slot>\n </div>\n</div>\n\n<div class=\"__menu-bar\">\n <ax-menu [orientation]=\"'horizontal'\" [class.ax-dark]=\"layoutService.isDarkMode()\" [hasArrow]=\"true\">\n @let sortedItems = sort(menuStore.items());\n @for (item of sortedItems; track item; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"recursiveMenu\" [ngTemplateOutletContext]=\"{ item: item, items: sortedItems, index: i }\"> </ng-container>\n }\n <!-- Item Templates -->\n <ng-template #recursiveMenu let-item=\"item\" let-items=\"items\" let-index=\"index\">\n @if (item.type == 'group') {\n @if (isItemVisible(item)) {\n <ax-title>{{ item.text | translate | async }}</ax-title>\n }\n } @else if (item.type == 'break') {\n @if (shouldShowDivider(item, items, index)) {\n <ax-divider></ax-divider>\n }\n } @else {\n @if (shouldRenderMenuItem(item)) {\n <ax-menu-item (onClick)=\"onMenuItemClick(item)\">\n @if (!item.meta?.isRoot) {\n <ax-prefix>\n <ax-icon [class]=\"item.icon\" class=\"fa-fw\"></ax-icon>\n </ax-prefix>\n }\n <ax-text> {{ item.text | translate | async }}</ax-text>\n @if (item.children?.length) {\n <ng-container>\n @let sortedChildren = sort(item.children);\n @for (child of sortedChildren; track child; let childIndex = $index) {\n <ng-container [ngTemplateOutlet]=\"recursiveMenu\" [ngTemplateOutletContext]=\"{ item: child, items: sortedChildren, index: childIndex }\">\n </ng-container>\n }\n </ng-container>\n }\n <ax-suffix>\n @if (badgeVisible()) {\n <ax-badge [axp-task-badge]=\"getMenuBadge(item)()\" color=\"secondary\"></ax-badge>\n }\n </ax-suffix>\n </ax-menu-item>\n }\n }\n </ng-template>\n </ax-menu>\n</div>\n<!-- <div class=\"__tab-bar\">\n<div class=\"axp-tabs-item axp-state-active\">\n <span class=\"ax-font-medium\">Account</span>\n <i class=\"fa-light fa-times ax-text-sm\"></i>\n</div>\n<div class=\"axp-tabs-item\">\n <span class=\"ax-font-medium\">Projects</span>\n\n</div>\n</div> -->\n", styles: [".__tab-bar{display:flex;height:3rem;width:100%;align-items:flex-end;gap:.5rem;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-darker-surface),var(--tw-bg-opacity, 1));padding-left:1rem;padding-right:1rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-darker-surface),var(--tw-text-opacity, 1))}.__tab-bar .axp-tabs-item{display:flex;height:2.5rem;cursor:pointer;align-items:center;justify-content:space-between;gap:.5rem;border-top-left-radius:.5rem;border-top-right-radius:.5rem;padding-left:1rem;padding-right:1rem;font-size:.875rem;line-height:1.25rem;line-height:1}.__tab-bar .axp-tabs-item:hover,.__tab-bar .axp-tabs-item.axp-state-active{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-lightest-surface),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-lightest-surface),var(--tw-text-opacity, 1))}.__menu-bar{display:flex;width:100%;align-items:flex-end;gap:.5rem;overflow-x:auto;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-dark-surface),var(--tw-bg-opacity, 1));padding:.375rem 1rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-dark-surface),var(--tw-text-opacity, 1))}\n"] }]
3553
+ }] });
3554
+
3555
+ class AXPRootHorizontalLayoutComponent {
3556
+ constructor() {
3557
+ this.config = inject(AXP_PLATFORM_CONFIG_TOKEN);
3558
+ this.logo = this.config.logo;
3559
+ this.store = inject(AXPLayoutThemeService);
3560
+ }
3561
+ handleCollapsedChange(value) {
3562
+ if (value) {
3563
+ this.store.closeSideMenu();
3564
+ }
3565
+ }
3566
+ handleResizingEnded(e) {
3567
+ if (e.isUserInteraction) {
3568
+ this.store.setSideMenuWidth(e.value);
3569
+ }
3570
+ }
3571
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootHorizontalLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3572
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRootHorizontalLayoutComponent, isStandalone: true, selector: "axp-root-horizontal-layout", viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true }], ngImport: i0, template: "<div class=\"axp-root-layout\">\n <ax-drawer-container>\n <ax-content class=\"ax-flex ax-flex-col ax-relative ax-bg-lightest dark:ax-bg-lighter\">\n @if(store.isNavigationLoading()) {\n <div class=\"axp-navigating-progress\">\n <div></div>\n </div>\n }\n <axp-horizontal-menu></axp-horizontal-menu>\n <div class=\"ax-flex-1 ax-overflow-auto ax-relative\">\n <router-outlet></router-outlet>\n </div>\n </ax-content>\n </ax-drawer-container>\n <axp-dashboard-admin-footer></axp-dashboard-admin-footer>\n</div>", styles: [".axp-root-layout{display:flex;height:100%;width:100%;flex-direction:column;justify-content:space-between}.axp-navigating-progress{position:absolute;top:0;height:.25rem;width:100%;overflow:hidden;background-color:rgba(var(--ax-sys-color-primary-200),.4)}.axp-navigating-progress>div{height:100%;width:100%;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));animation:indeterminateAnimation 1.5s infinite cubic-bezier(.65,.815,.735,.395);transform-origin:0% 50%}@keyframes indeterminateAnimation{0%{transform:translate(-100%) scaleX(.2)}50%{transform:translate(50%) scaleX(.6)}to{transform:translate(100%) scaleX(.4)}}axp-dashboard-admin-header{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-surface),var(--tw-text-opacity, 1))}axp-dashboard-admin-header ax-button{--ax-comp-button-normal-bg-color: 0, 0, 0, 0 !important;--ax-comp-button-border-width: 0 !important;--ax-comp-button-normal-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-hover-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-focus-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-pressed-text-color: var(--axp-on-brand-color) !important}.resize-handle{background-color:transparent!important}.resize-handle:hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$6.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "ngmodule", type: AXImageModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "ngmodule", type: AXDrawerModule }, { kind: "component", type: i3$4.AXDrawerContainerComponent, selector: "ax-drawer-container" }, { kind: "ngmodule", type: AXSideMenuModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXPAuthModule }, { kind: "component", type: AXPRootLayoutFooterComponent, selector: "axp-dashboard-admin-footer" }, { kind: "component", type: AXPHorizontalMenuComponent, selector: "axp-horizontal-menu" }, { kind: "ngmodule", type: AXPComponentSlotModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3573
+ }
3574
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootHorizontalLayoutComponent, decorators: [{
3575
+ type: Component,
3576
+ args: [{ selector: 'axp-root-horizontal-layout', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
3577
+ RouterModule,
3578
+ AXButtonModule,
3579
+ AXTranslationModule,
3580
+ AXDecoratorModule,
3581
+ AXDropdownModule,
3582
+ AXAvatarModule,
3583
+ AXImageModule,
3584
+ AXImageModule,
3585
+ AXPopoverModule,
3586
+ AXBadgeModule,
3587
+ AXTabsModule,
3588
+ AXDrawerModule,
3589
+ AXSideMenuModule,
3590
+ AXLoadingModule,
3591
+ AXPAuthModule,
3592
+ AXPRootLayoutFooterComponent,
3593
+ AXPHorizontalMenuComponent,
3594
+ AXPComponentSlotModule
3595
+ ], template: "<div class=\"axp-root-layout\">\n <ax-drawer-container>\n <ax-content class=\"ax-flex ax-flex-col ax-relative ax-bg-lightest dark:ax-bg-lighter\">\n @if(store.isNavigationLoading()) {\n <div class=\"axp-navigating-progress\">\n <div></div>\n </div>\n }\n <axp-horizontal-menu></axp-horizontal-menu>\n <div class=\"ax-flex-1 ax-overflow-auto ax-relative\">\n <router-outlet></router-outlet>\n </div>\n </ax-content>\n </ax-drawer-container>\n <axp-dashboard-admin-footer></axp-dashboard-admin-footer>\n</div>", styles: [".axp-root-layout{display:flex;height:100%;width:100%;flex-direction:column;justify-content:space-between}.axp-navigating-progress{position:absolute;top:0;height:.25rem;width:100%;overflow:hidden;background-color:rgba(var(--ax-sys-color-primary-200),.4)}.axp-navigating-progress>div{height:100%;width:100%;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));animation:indeterminateAnimation 1.5s infinite cubic-bezier(.65,.815,.735,.395);transform-origin:0% 50%}@keyframes indeterminateAnimation{0%{transform:translate(-100%) scaleX(.2)}50%{transform:translate(50%) scaleX(.6)}to{transform:translate(100%) scaleX(.4)}}axp-dashboard-admin-header{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-surface),var(--tw-text-opacity, 1))}axp-dashboard-admin-header ax-button{--ax-comp-button-normal-bg-color: 0, 0, 0, 0 !important;--ax-comp-button-border-width: 0 !important;--ax-comp-button-normal-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-hover-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-focus-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-pressed-text-color: var(--axp-on-brand-color) !important}.resize-handle{background-color:transparent!important}.resize-handle:hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}\n"] }]
3596
+ }], propDecorators: { drawer: [{
3597
+ type: ViewChild,
3598
+ args: ['drawer']
3599
+ }] } });
3600
+
3601
+ class AXPRootVerticalLayoutComponent {
3602
+ constructor() {
3603
+ this.config = inject(AXP_PLATFORM_CONFIG_TOKEN);
3604
+ this.logo = this.config.logo;
3605
+ this.store = inject(AXPLayoutThemeService);
3606
+ this.deviceService = inject(AXPDeviceService);
3607
+ this.router = inject(Router);
3608
+ /** Recreates the drawer when switching overlay ↔ push to reset internal layout sizing. */
3609
+ this.sideMenuDrawerKey = computed(() => this.deviceService.isSmall() || this.deviceService.isMedium() ? 'overlay' : 'push', ...(ngDevMode ? [{ debugName: "sideMenuDrawerKey" }] : /* istanbul ignore next */ []));
3610
+ this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
3611
+ if (this.sideMenuDrawerKey() === 'overlay') {
3612
+ this.store.closeSideMenu();
3613
+ }
3614
+ });
3615
+ }
3616
+ handleResizingEnded(e) {
3617
+ if (e.isUserInteraction) {
3618
+ this.store.setSideMenuDrawerWidth(e.value);
3619
+ }
3620
+ }
3621
+ handleBackdropClick() {
3622
+ this.store.closeSideMenu();
3623
+ }
3624
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootVerticalLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3625
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRootVerticalLayoutComponent, isStandalone: true, selector: "axp-root-vertical-layout", ngImport: i0, template: "<div id=\"axp-root-layout\" class=\"axp-root-layout\">\n <ax-drawer-container>\n @for (drawerKey of [sideMenuDrawerKey()]; track drawerKey) {\n <ax-drawer id=\"axp-side-menu-drawer\" [axResizable]=\"drawerKey === 'push'\"\n class=\"ax-border-lighter ax-bg-lighter dark:ax-border-darkest dark:ax-bg-darkest ax-h-full !ax-flex ax-flex-col\"\n [class.ax-w-[75%]]=\"drawerKey === 'overlay'\" [width]=\"store.sideMenuDrawerWidth()\" location=\"start\"\n [collapsed]=\"!store.isSideMenuOpen()\" [closeOnBackdropClick]=\"false\" [defaultWidth]=\"store.sideMenuDrawerWidth()\"\n [mode]=\"drawerKey === 'overlay' ? 'overlay' : 'push'\" [dblClickAction]=\"'fit'\"\n (onResizingEnded)=\"handleResizingEnded($event)\" (onResizingDblClick)=\"handleResizingEnded($event)\"\n (onBackdropClick)=\"handleBackdropClick()\" backdropClass=\"ax-bg-black/50\">\n <ax-header id=\"axp-side-menu-header\"\n class=\"ax-light ax-h-14 ax-flex ax-items-center ax-justify-center ax-bg-primary-500 ax-text-primary-on-surface ax-border-e ax-border-primary-600\">\n <axp-logo [source]=\"logo?.full?.dark\"></axp-logo>\n </ax-header>\n <ax-content class=\"ax-flex ax-flex-col ax-border-e ax-h-full ax-overflow-y-auto ax-overflow-x-hidden\">\n <axp-component-slot name=\"root-side-menu-header\"></axp-component-slot>\n <div class=\"ax-flex-1\">\n <axp-side-menu id=\"axp-side-menu\" class=\"ax-p-4 ax-block\"></axp-side-menu>\n </div>\n <axp-component-slot name=\"root-side-menu-footer\"></axp-component-slot>\n </ax-content>\n </ax-drawer>\n }\n <ax-content class=\"ax-flex ax-flex-col ax-relative ax-bg-lightest dark:ax-bg-lighter ax-overflow-auto\">\n @if (store.isNavigationLoading()) {\n <div class=\"axp-navigating-progress\">\n <div></div>\n </div>\n }\n <axp-dashboard-admin-header id=\"axp-main-header\" class=\"ax-light\"></axp-dashboard-admin-header>\n\n <div class=\"ax-flex-1 ax-overflow-auto ax-relative\">\n <router-outlet></router-outlet>\n </div>\n </ax-content>\n </ax-drawer-container>\n <axp-component-slot name=\"root-layout-overlay\"></axp-component-slot>\n <axp-dashboard-admin-footer id=\"axp-main-footer\"></axp-dashboard-admin-footer>\n</div>", styles: [".axp-root-layout{display:flex;height:100%;width:100%;flex-direction:column;justify-content:space-between}#axp-side-menu-drawer{box-sizing:border-box}.axp-navigating-progress{position:absolute;top:0;height:.25rem;width:100%;overflow:hidden;background-color:rgba(var(--ax-sys-color-primary-200),.4)}.axp-navigating-progress>div{height:100%;width:100%;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));animation:indeterminateAnimation 1.5s infinite cubic-bezier(.65,.815,.735,.395);transform-origin:0% 50%}@keyframes indeterminateAnimation{0%{transform:translate(-100%) scaleX(.2)}50%{transform:translate(50%) scaleX(.6)}to{transform:translate(100%) scaleX(.4)}}axp-dashboard-admin-header{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-surface),var(--tw-text-opacity, 1))}axp-dashboard-admin-header ax-button{--ax-comp-button-normal-bg-color: 0, 0, 0, 0 !important;--ax-comp-button-border-width: 0 !important;--ax-comp-button-normal-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-hover-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-focus-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-pressed-text-color: var(--axp-on-brand-color) !important}.resize-handle{background-color:transparent!important}.resize-handle:hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$6.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "ngmodule", type: AXImageModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "ngmodule", type: AXDrawerModule }, { kind: "component", type: i3$4.AXDrawerComponent, selector: "ax-drawer", inputs: ["location", "showBackdrop", "mode", "transition", "closeOnBackdropClick", "backdropClass", "collapsed", "singleOpenMode"], outputs: ["onBackdropClick", "collapsedStateChanged"] }, { kind: "component", type: i3$4.AXDrawerContainerComponent, selector: "ax-drawer-container" }, { kind: "ngmodule", type: AXSideMenuModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: AXPLogoComponent, selector: "axp-logo", inputs: ["source"] }, { kind: "ngmodule", type: AXPAuthModule }, { kind: "directive", type: AXResizableDirective, selector: "[axResizable]", inputs: ["axResizable", "minWidth", "maxWidth", "dblClickAction", "width", "defaultWidth"], outputs: ["axResizableChange", "minWidthChange", "maxWidthChange", "dblClickActionChange", "widthChange", "defaultWidthChange", "onResizingStarted", "onResizingEnded", "onResizingDblClick"] }, { kind: "component", type: AXPRootLayoutFooterComponent, selector: "axp-dashboard-admin-footer" }, { kind: "component", type: AXPRootLayoutHeaderComponent, selector: "axp-dashboard-admin-header" }, { kind: "component", type: AXPRootLayoutMenuComponent, selector: "axp-side-menu" }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i1$5.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3626
+ }
3627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootVerticalLayoutComponent, decorators: [{
3628
+ type: Component,
3629
+ args: [{ selector: 'axp-root-vertical-layout', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
3630
+ RouterModule,
3631
+ AXButtonModule,
3632
+ AXTranslationModule,
3633
+ AXDecoratorModule,
3634
+ AXDropdownModule,
3635
+ AXAvatarModule,
3636
+ AXImageModule,
3637
+ AXImageModule,
3638
+ AXPopoverModule,
3639
+ AXBadgeModule,
3640
+ AXTabsModule,
3641
+ AXDrawerModule,
3642
+ AXDrawerModule,
3643
+ AXSideMenuModule,
3644
+ AXLoadingModule,
3645
+ AXPLogoComponent,
3646
+ AXPAuthModule,
3647
+ AXResizableDirective,
3648
+ AXPRootLayoutFooterComponent,
3649
+ AXPRootLayoutHeaderComponent,
3650
+ AXPRootLayoutMenuComponent,
3651
+ AXPComponentSlotModule,
3652
+ ], template: "<div id=\"axp-root-layout\" class=\"axp-root-layout\">\n <ax-drawer-container>\n @for (drawerKey of [sideMenuDrawerKey()]; track drawerKey) {\n <ax-drawer id=\"axp-side-menu-drawer\" [axResizable]=\"drawerKey === 'push'\"\n class=\"ax-border-lighter ax-bg-lighter dark:ax-border-darkest dark:ax-bg-darkest ax-h-full !ax-flex ax-flex-col\"\n [class.ax-w-[75%]]=\"drawerKey === 'overlay'\" [width]=\"store.sideMenuDrawerWidth()\" location=\"start\"\n [collapsed]=\"!store.isSideMenuOpen()\" [closeOnBackdropClick]=\"false\" [defaultWidth]=\"store.sideMenuDrawerWidth()\"\n [mode]=\"drawerKey === 'overlay' ? 'overlay' : 'push'\" [dblClickAction]=\"'fit'\"\n (onResizingEnded)=\"handleResizingEnded($event)\" (onResizingDblClick)=\"handleResizingEnded($event)\"\n (onBackdropClick)=\"handleBackdropClick()\" backdropClass=\"ax-bg-black/50\">\n <ax-header id=\"axp-side-menu-header\"\n class=\"ax-light ax-h-14 ax-flex ax-items-center ax-justify-center ax-bg-primary-500 ax-text-primary-on-surface ax-border-e ax-border-primary-600\">\n <axp-logo [source]=\"logo?.full?.dark\"></axp-logo>\n </ax-header>\n <ax-content class=\"ax-flex ax-flex-col ax-border-e ax-h-full ax-overflow-y-auto ax-overflow-x-hidden\">\n <axp-component-slot name=\"root-side-menu-header\"></axp-component-slot>\n <div class=\"ax-flex-1\">\n <axp-side-menu id=\"axp-side-menu\" class=\"ax-p-4 ax-block\"></axp-side-menu>\n </div>\n <axp-component-slot name=\"root-side-menu-footer\"></axp-component-slot>\n </ax-content>\n </ax-drawer>\n }\n <ax-content class=\"ax-flex ax-flex-col ax-relative ax-bg-lightest dark:ax-bg-lighter ax-overflow-auto\">\n @if (store.isNavigationLoading()) {\n <div class=\"axp-navigating-progress\">\n <div></div>\n </div>\n }\n <axp-dashboard-admin-header id=\"axp-main-header\" class=\"ax-light\"></axp-dashboard-admin-header>\n\n <div class=\"ax-flex-1 ax-overflow-auto ax-relative\">\n <router-outlet></router-outlet>\n </div>\n </ax-content>\n </ax-drawer-container>\n <axp-component-slot name=\"root-layout-overlay\"></axp-component-slot>\n <axp-dashboard-admin-footer id=\"axp-main-footer\"></axp-dashboard-admin-footer>\n</div>", styles: [".axp-root-layout{display:flex;height:100%;width:100%;flex-direction:column;justify-content:space-between}#axp-side-menu-drawer{box-sizing:border-box}.axp-navigating-progress{position:absolute;top:0;height:.25rem;width:100%;overflow:hidden;background-color:rgba(var(--ax-sys-color-primary-200),.4)}.axp-navigating-progress>div{height:100%;width:100%;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));animation:indeterminateAnimation 1.5s infinite cubic-bezier(.65,.815,.735,.395);transform-origin:0% 50%}@keyframes indeterminateAnimation{0%{transform:translate(-100%) scaleX(.2)}50%{transform:translate(50%) scaleX(.6)}to{transform:translate(100%) scaleX(.4)}}axp-dashboard-admin-header{--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-on-primary-surface),var(--tw-text-opacity, 1))}axp-dashboard-admin-header ax-button{--ax-comp-button-normal-bg-color: 0, 0, 0, 0 !important;--ax-comp-button-border-width: 0 !important;--ax-comp-button-normal-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-hover-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-focus-text-color: var(--axp-on-brand-color) !important;--ax-comp-button-pressed-text-color: var(--axp-on-brand-color) !important}.resize-handle{background-color:transparent!important}.resize-handle:hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}\n"] }]
3653
+ }], ctorParameters: () => [] });
3654
+
3655
+ class AXPRootLayoutComponent {
3656
+ /** Only show the loading dialog when overlay loading stays active longer than this. */
3657
+ static { this.overlayLoadingShowDelayMs = 2000; }
3658
+ constructor() {
3659
+ this.layout = inject(AXPLayoutThemeService);
3660
+ this.menuStore = inject(AXPMenuService);
3661
+ this.router = inject(Router);
3662
+ this.eventService = inject(AXPBroadcastEventService);
3663
+ this.sessionService = inject(AXPSessionService);
3664
+ this.unsubscriber = inject(AXUnsubscriber);
3665
+ this.contextStore = inject(AXPContextStore);
3666
+ this.loadingDialog = inject(AXLoadingDialogService);
3667
+ this.translationService = inject(AXTranslationService);
3668
+ this.destroyRef = inject(DestroyRef);
3669
+ this.overlayLoadingDialogRef = null;
3670
+ this.overlayLoadingShowTimer = null;
3671
+ effect(() => {
3672
+ const isLoading = this.layout.isOverlayLoading();
3673
+ untracked(() => {
3674
+ this.syncOverlayLoadingDialog(isLoading);
3675
+ });
3676
+ });
3677
+ this.destroyRef.onDestroy(() => {
3678
+ this.cancelOverlayLoadingShowTimer();
3679
+ this.closeOverlayLoadingDialog();
3680
+ });
3681
+ }
3682
+ syncOverlayLoadingDialog(isLoading) {
3683
+ if (isLoading) {
3684
+ if (this.overlayLoadingDialogRef || this.overlayLoadingShowTimer) {
3685
+ return;
3686
+ }
3687
+ this.overlayLoadingShowTimer = setTimeout(() => {
3688
+ this.overlayLoadingShowTimer = null;
3689
+ if (!this.layout.isOverlayLoading()) {
3690
+ return;
3691
+ }
3692
+ void this.openOverlayLoadingDialog();
3693
+ }, AXPRootLayoutComponent.overlayLoadingShowDelayMs);
3694
+ return;
3695
+ }
3696
+ this.cancelOverlayLoadingShowTimer();
3697
+ this.closeOverlayLoadingDialog();
3698
+ }
3699
+ async openOverlayLoadingDialog() {
3700
+ if (this.overlayLoadingDialogRef || !this.layout.isOverlayLoading()) {
3701
+ return;
3702
+ }
3703
+ this.overlayLoadingDialogRef = this.loadingDialog.show({
3704
+ title: await this.translationService.translateAsync('@general:state-message.loading.title'),
3705
+ mode: 'indeterminate',
3706
+ text: await this.translationService.translateAsync('@general:state-message.loading.description'),
3707
+ });
3708
+ }
3709
+ cancelOverlayLoadingShowTimer() {
3710
+ if (this.overlayLoadingShowTimer) {
3711
+ clearTimeout(this.overlayLoadingShowTimer);
3712
+ this.overlayLoadingShowTimer = null;
3713
+ }
3714
+ }
3715
+ closeOverlayLoadingDialog() {
3716
+ this.overlayLoadingDialogRef?.close();
3717
+ this.overlayLoadingDialogRef = null;
3718
+ }
3719
+ ngOnInit() {
3720
+ this.#syncMenuSelectionByRoute(this.router.url);
3721
+ this.router.events
3722
+ .pipe(filter((event) => event instanceof NavigationEnd), this.unsubscriber.takeUntilDestroy)
3723
+ .subscribe((event) => {
3724
+ this.#syncMenuSelectionByRoute(event.urlAfterRedirects);
3725
+ this.contextStore.reset();
3726
+ });
3727
+ this.eventService.listen(AXPSessionStatus.SignedOut).subscribe(() => {
3728
+ console.log('signed out in root layout');
3729
+ this.router.navigate(['/auth/login']);
3730
+ });
3731
+ //
3732
+ this.sessionService.status$.pipe(this.unsubscriber.takeUntilDestroy).subscribe(async (status) => {
3733
+ // Don't redirect if still loading
3734
+ const isLoading = await firstValueFrom(this.sessionService.isLoading$);
3735
+ if (isLoading) {
3736
+ console.log('Session is still loading, skipping redirect check');
3737
+ return;
3738
+ }
3739
+ const redirectKeys = [AXPSessionStatus.SignedOut, AXPSessionStatus.Unauthenticated, AXPSessionStatus.Expired];
3740
+ if (redirectKeys.includes(status)) {
3741
+ console.log('Redirecting to login due to status:', status);
3742
+ this.router.navigate(['/auth/login']);
3743
+ }
3744
+ });
3745
+ }
3746
+ #syncMenuSelectionByRoute(url) {
3747
+ this.menuStore.selectMenuItemByRoute(url);
3748
+ }
3749
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3750
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPRootLayoutComponent, isStandalone: true, selector: "ng-component", providers: [AXUnsubscriber, AXPContextStore], ngImport: i0, template: `
3751
+ @if (layout.isMenuHorizontal()) {
3752
+ <axp-root-horizontal-layout></axp-root-horizontal-layout>
3753
+ } @else {
3754
+ <axp-root-vertical-layout></axp-root-vertical-layout>
3755
+ }
3756
+ `, isInline: true, dependencies: [{ kind: "component", type: AXPRootHorizontalLayoutComponent, selector: "axp-root-horizontal-layout" }, { kind: "component", type: AXPRootVerticalLayoutComponent, selector: "axp-root-vertical-layout" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3757
+ }
3758
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPRootLayoutComponent, decorators: [{
3759
+ type: Component,
3760
+ args: [{
3761
+ template: `
3762
+ @if (layout.isMenuHorizontal()) {
3763
+ <axp-root-horizontal-layout></axp-root-horizontal-layout>
3764
+ } @else {
3765
+ <axp-root-vertical-layout></axp-root-vertical-layout>
3766
+ }
3767
+ `,
3768
+ encapsulation: ViewEncapsulation.None,
3769
+ changeDetection: ChangeDetectionStrategy.OnPush,
3770
+ standalone: true,
3771
+ imports: [AXPRootHorizontalLayoutComponent, AXPRootVerticalLayoutComponent],
3772
+ providers: [AXUnsubscriber, AXPContextStore],
3773
+ }]
3774
+ }], ctorParameters: () => [] });
3775
+
3776
+ var rootLayout_component = /*#__PURE__*/Object.freeze({
3777
+ __proto__: null,
3778
+ AXPRootLayoutComponent: AXPRootLayoutComponent
3779
+ });
3780
+
3781
+ /**
3782
+ * Route reuse strategy that powers both same-route reuse (`reuse` route data) and tabular
3783
+ * navigation. In tabular mode it detaches the leaf page component of an open tab when the user
3784
+ * switches away, caches the live component instance, and reattaches it on return so state such as
3785
+ * data-table selection, scroll position, and unsaved form values is preserved — like browser tabs.
3786
+ */
3787
+ class AXPEntityReuseStrategy {
3788
+ constructor() {
3789
+ //#region ---- Services & Dependencies ----
3790
+ this.routeCache = inject(AXPTabRouteCacheRegistry);
3791
+ /** Lazy lookup avoids Router ↔ RouteReuseStrategy construction cycle (NG0200). */
3792
+ this.injector = inject(Injector);
3793
+ //#endregion
3794
+ }
3795
+ //#endregion
3796
+ //#region ---- Detach / Attach ----
3797
+ shouldDetach(route) {
3798
+ if (!this.routeCache.isEnabled() || !this.isTabCacheRoute(route)) {
3799
+ return false;
3800
+ }
3801
+ const scopedKey = this.scopedTabKeyFor(route);
3802
+ const cacheKey = this.routeCacheKeyFor(route);
3803
+ if (!scopedKey || !cacheKey || !this.routeCache.isOpen(scopedKey)) {
3804
+ return false;
3805
+ }
3806
+ // Keep the live component when navigating within the same scoped tab (e.g. chat → chat/:id).
3807
+ return this.isLeavingScopedTab(route);
3808
+ }
3809
+ store(route, handle) {
3810
+ if (!this.routeCache.isEnabled()) {
3811
+ return;
3812
+ }
3813
+ const key = this.routeCacheKeyFor(route);
3814
+ if (!key) {
3815
+ return;
3816
+ }
3817
+ // A null handle is Angular releasing a just-reattached view; drop it without destroying.
3818
+ if (handle) {
3819
+ this.routeCache.store(key, handle);
3820
+ }
3821
+ else {
3822
+ this.routeCache.release(key);
3823
+ }
3824
+ }
3825
+ shouldAttach(route) {
3826
+ if (!this.routeCache.isEnabled() || !this.isTabCacheRoute(route)) {
3827
+ return false;
3828
+ }
3829
+ const key = this.routeCacheKeyFor(route);
3830
+ return !!key && this.routeCache.has(key);
3831
+ }
3832
+ retrieve(route) {
3833
+ if (!this.routeCache.isEnabled() || !this.isTabCacheRoute(route)) {
3834
+ return null;
3835
+ }
3836
+ const key = this.routeCacheKeyFor(route);
3837
+ return key ? this.routeCache.retrieve(key) : null;
3838
+ }
3839
+ //#endregion
3840
+ //#region ---- Reuse Decision ----
3841
+ shouldReuseRoute(future, curr) {
3842
+ // Reuse the single root-layout shell across module navigations so the heavy shell (menus,
3843
+ // header, session subscriptions, theme) is not destroyed and recreated on every route switch.
3844
+ // Uses `routeConfig.data` (the node's own data) so inherited child data does not leak this flag.
3845
+ if (future.routeConfig?.data?.['rootLayout'] === true && curr.routeConfig?.data?.['rootLayout'] === true) {
3846
+ return true;
3847
+ }
3848
+ const reuseConfig = future.data?.['reuse'];
3849
+ if (reuseConfig === false) {
3850
+ return false;
3851
+ }
3852
+ // `reuse: true` on sibling routes under the same scoped tab (e.g. chat '' ↔ :id).
3853
+ if (reuseConfig === true) {
3854
+ // Same route node re-activated (e.g. settings `:scope` change reusing its `setting-page`).
3855
+ // Use config reference equality, not path strings: different tabs can share a path (e.g. the
3856
+ // empty-path child of both settings and chat), and once the root-layout shell is reused those
3857
+ // siblings get compared directly — a path-only match would wrongly keep the previous content.
3858
+ if (future.routeConfig && future.routeConfig === curr.routeConfig) {
3859
+ return true;
3860
+ }
3861
+ if (curr.data?.['reuse'] === true) {
3862
+ return axpResolveTabKeyFromSnapshot(future) === axpResolveTabKeyFromSnapshot(curr);
3863
+ }
3864
+ return false;
3865
+ }
3866
+ // Baseline "same route node" check. Reference equality (not path strings) so same-path children
3867
+ // of different reused shells (e.g. the empty-path child of notifications vs comments) are treated
3868
+ // as distinct and their content swaps correctly.
3869
+ if (future.routeConfig !== curr.routeConfig) {
3870
+ return false;
3871
+ }
3872
+ if (typeof reuseConfig === 'string') {
3873
+ const paramsChanged = this.haveParamsChanged(future, curr);
3874
+ const queryParamsChanged = this.haveQueryParamsChanged(future, curr);
3875
+ switch (reuseConfig) {
3876
+ case 'paramsChange':
3877
+ return !queryParamsChanged;
3878
+ case 'queryParamsChange':
3879
+ return !paramsChanged;
3880
+ case 'paramsOrQueryParamsChange':
3881
+ return true;
3882
+ default:
3883
+ return true;
3884
+ }
3885
+ }
3886
+ return true;
3887
+ }
3888
+ //#endregion
3889
+ //#region ---- Helper Methods ----
3890
+ /**
3891
+ * True when this route is the detach/attach target: a routable leaf, or a scoped tab shell
3892
+ * (`data.tab = true`) that owns a nested outlet (e.g. settings page).
3893
+ */
3894
+ isTabCacheRoute(route) {
3895
+ return this.resolveTabCacheTarget(route) === route;
3896
+ }
3897
+ /**
3898
+ * Under a scoped tab, cache the `reuse: true` page shell (settings page) instead of nested outlet
3899
+ * children (setting-view). Otherwise cache the routable leaf (entity pages, chat).
3900
+ */
3901
+ resolveTabCacheTarget(route) {
3902
+ const shell = this.findScopedShellRoute(route);
3903
+ if (shell) {
3904
+ // A scoped shell that owns a nested routed outlet (e.g. the settings page, whose `:group`
3905
+ // child renders `setting-view`) cannot be safely detached and reattached: Angular does not
3906
+ // resume the nested child's `effect()`s / change detection after reattach, so dirty tracking
3907
+ // and child content swaps silently stop working. Skip caching these shells — they recreate
3908
+ // fresh on tab return (the active child is preserved via the URL). Flat leaf shells (e.g. the
3909
+ // chat component) have no nested outlet and reattach correctly, so they stay cached.
3910
+ if (this.shellOwnsNestedOutlet(shell)) {
3911
+ return null;
3912
+ }
3913
+ return shell;
3914
+ }
3915
+ if (this.isRoutableLeaf(route)) {
3916
+ return route;
3917
+ }
3918
+ return null;
3919
+ }
3920
+ /** True when the scoped shell route declares child routes (i.e. it hosts a nested outlet). */
3921
+ shellOwnsNestedOutlet(shell) {
3922
+ const config = shell.routeConfig;
3923
+ return !!(config?.children?.length || config?.loadChildren);
3924
+ }
3925
+ /** Nearest ancestor route that declares `data.tab = true`. */
3926
+ findScopedAncestor(route) {
3927
+ for (let index = route.pathFromRoot.length - 1; index >= 0; index--) {
3928
+ const snapshot = route.pathFromRoot[index];
3929
+ if (axpHasTabScope(snapshot.data)) {
3930
+ return snapshot;
3931
+ }
3932
+ }
3933
+ return null;
3934
+ }
3935
+ /**
3936
+ * The innermost `reuse: true` component route between a scoped ancestor and the active route.
3937
+ * Settings: `setting-page` (not `setting-view`). Chat: the active chat child route.
3938
+ */
3939
+ findScopedShellRoute(route) {
3940
+ const scope = this.findScopedAncestor(route);
3941
+ if (!scope) {
3942
+ return null;
3943
+ }
3944
+ const chain = route.pathFromRoot;
3945
+ const scopeIndex = chain.indexOf(scope);
3946
+ if (scopeIndex < 0) {
3947
+ return null;
3948
+ }
3949
+ let shell = null;
3950
+ for (let index = scopeIndex + 1; index < chain.length; index++) {
3951
+ const snapshot = chain[index];
3952
+ if (snapshot.data?.['reuse'] === true && this.hasRoutableComponent(snapshot)) {
3953
+ shell = snapshot;
3954
+ }
3955
+ }
3956
+ return shell;
3957
+ }
3958
+ isRoutableLeaf(route) {
3959
+ if (route.firstChild) {
3960
+ return false;
3961
+ }
3962
+ return this.hasRoutableComponent(route);
3963
+ }
3964
+ hasRoutableComponent(route) {
3965
+ const config = route.routeConfig;
3966
+ return !!(route.component || config?.component || config?.loadComponent);
3967
+ }
3968
+ /** Cache key — scoped tab path for shells, full leaf path otherwise. */
3969
+ routeCacheKeyFor(route) {
3970
+ if (!this.isTabCacheRoute(route)) {
3971
+ return null;
3972
+ }
3973
+ const path = this.findScopedShellRoute(route) ? axpResolveTabKeyFromSnapshot(route) : axpPathFromSnapshot(route);
3974
+ return axpIsTabEligibleUrl(path) ? path : null;
3975
+ }
3976
+ /** Scoped tab key — matches open tab descriptors (`data.tab = true`). */
3977
+ scopedTabKeyFor(route) {
3978
+ const target = this.resolveTabCacheTarget(route);
3979
+ if (!target) {
3980
+ return null;
3981
+ }
3982
+ const path = axpResolveTabKeyFromSnapshot(target);
3983
+ return axpIsTabEligibleUrl(path) ? path : null;
3984
+ }
3985
+ /** True when the in-flight navigation leaves the scoped tab that owns this route. */
3986
+ isLeavingScopedTab(route) {
3987
+ const currentScoped = axpResolveTabKeyFromSnapshot(route);
3988
+ const router = this.injector.get(Router, null);
3989
+ if (!router) {
3990
+ return true;
3991
+ }
3992
+ const navigation = router.getCurrentNavigation();
3993
+ if (!navigation?.finalUrl) {
3994
+ return true;
3995
+ }
3996
+ const targetPath = router.serializeUrl(navigation.finalUrl).split('?')[0].split('#')[0];
3997
+ const futureScoped = axpResolveTabKeyFromRouteConfig(router.config, targetPath) ??
3998
+ (targetPath.startsWith('/') ? targetPath : `/${targetPath}`);
3999
+ return currentScoped !== futureScoped;
4000
+ }
4001
+ haveParamsChanged(future, curr) {
4002
+ const futureParams = future.params;
4003
+ const currParams = curr.params;
4004
+ const futureKeys = Object.keys(futureParams);
4005
+ const currKeys = Object.keys(currParams);
4006
+ if (futureKeys.length !== currKeys.length) {
4007
+ return true;
4008
+ }
4009
+ for (const key of futureKeys) {
4010
+ if (futureParams[key] !== currParams[key]) {
4011
+ return true;
4012
+ }
4013
+ }
4014
+ return false;
4015
+ }
4016
+ haveQueryParamsChanged(future, curr) {
4017
+ const futureQueryParams = future.queryParams;
4018
+ const currQueryParams = curr.queryParams;
4019
+ const futureKeys = Object.keys(futureQueryParams);
4020
+ const currKeys = Object.keys(currQueryParams);
4021
+ if (futureKeys.length !== currKeys.length) {
4022
+ return true;
4023
+ }
4024
+ for (const key of futureKeys) {
4025
+ if (futureQueryParams[key] !== currQueryParams[key]) {
4026
+ return true;
4027
+ }
4028
+ }
4029
+ return false;
4030
+ }
4031
+ }
4032
+
4033
+ function routesFacory() {
4034
+ const config = inject(AXP_ENTITY_CONFIG_TOKEN);
4035
+ let routes = [
4036
+ {
4037
+ path: ':app/modules/:module/:entity',
4038
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
4039
+ data: { rootLayout: true },
4040
+ loadComponent: () => {
4041
+ return config.viewers.root();
4042
+ },
4043
+ // children: [
4044
+ // {
4045
+ // path: 'details/:id',
4046
+ // loadComponent: () => import('@acorex-platform/framework-client/layout/views').then((c) => c.AXPLayoutDetailsViewComponent),
4047
+ // data: { reuse: true },
4048
+ // resolve: {
4049
+ // adapter: AXPLayoutDetailsViewRouteResolver,
4050
+ // },
4051
+ // },
4052
+ // ],
4053
+ },
4054
+ {
4055
+ path: 'error',
4056
+ children: [
4057
+ {
4058
+ path: 'offline',
4059
+ loadComponent: () => import('./acorex-platform-framework-client-themes-default-error-offline.component-DR6G8gPC.mjs').then((c) => c.AXPErrorOfflineComponent),
4060
+ },
4061
+ {
4062
+ path: '404',
4063
+ loadComponent: () => import('./acorex-platform-framework-client-themes-default-error-404.component-Byyqk8pH.mjs').then((c) => c.AXPError404Component),
4064
+ },
4065
+ {
4066
+ path: '401',
4067
+ loadComponent: () => import('./acorex-platform-framework-client-themes-default-error-401.component-D--_MIar.mjs').then((c) => c.AXPError401Component),
4068
+ },
4069
+ ],
4070
+ },
4071
+ {
4072
+ path: '404',
4073
+ redirectTo: 'error/404',
4074
+ pathMatch: 'full',
4075
+ },
4076
+ {
4077
+ path: '401',
4078
+ redirectTo: 'error/401',
4079
+ pathMatch: 'full',
4080
+ },
4081
+ ];
4082
+ return routes;
4083
+ }
4084
+ class AXPDefaultThemeModule {
4085
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultThemeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
4086
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultThemeModule, imports: [AXPCommonModule, AXPThemesSharedModule] }); }
4087
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultThemeModule, providers: [
4088
+ {
4089
+ provide: AXP_ENTITY_CONFIG_TOKEN,
4090
+ useFactory: () => {
4091
+ const config = {
4092
+ viewers: {
4093
+ root: () => Promise.resolve().then(function () { return rootLayout_component; }).then((c) => c.AXPRootLayoutComponent),
4094
+ master: {
4095
+ single: () => import('./acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs').then((c) => c.AXPEntityMasterSingleViewComponent),
4096
+ details: () => import('@acorex-platform/framework-client/layout/views').then((c) => c.AXPLayoutDetailsViewComponent),
4097
+ ///
4098
+ create: () => import('./acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs').then((c) => c.AXPEntityMasterCreateViewComponent),
4099
+ ///
4100
+ modify: () => import('./acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs').then((c) => c.AXPEntityMasterModifyViewComponent),
4101
+ //
4102
+ list: () => Promise.resolve().then(function () { return entityMasterListView_component; }).then((c) => c.AXPEntityMasterListViewComponent),
4103
+ },
4104
+ },
4105
+ };
4106
+ return config;
4107
+ },
4108
+ },
4109
+ { provide: RouteReuseStrategy, useClass: AXPEntityReuseStrategy },
4110
+ {
4111
+ provide: ROUTES,
4112
+ multi: true,
4113
+ useFactory: routesFacory,
4114
+ },
4115
+ ], imports: [AXPCommonModule, AXPThemesSharedModule] }); }
4116
+ }
4117
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultThemeModule, decorators: [{
4118
+ type: NgModule,
4119
+ args: [{
4120
+ imports: [AXPCommonModule, AXPThemesSharedModule],
4121
+ exports: [],
4122
+ declarations: [],
4123
+ providers: [
4124
+ {
4125
+ provide: AXP_ENTITY_CONFIG_TOKEN,
4126
+ useFactory: () => {
4127
+ const config = {
4128
+ viewers: {
4129
+ root: () => Promise.resolve().then(function () { return rootLayout_component; }).then((c) => c.AXPRootLayoutComponent),
4130
+ master: {
4131
+ single: () => import('./acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs').then((c) => c.AXPEntityMasterSingleViewComponent),
4132
+ details: () => import('@acorex-platform/framework-client/layout/views').then((c) => c.AXPLayoutDetailsViewComponent),
4133
+ ///
4134
+ create: () => import('./acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs').then((c) => c.AXPEntityMasterCreateViewComponent),
4135
+ ///
4136
+ modify: () => import('./acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs').then((c) => c.AXPEntityMasterModifyViewComponent),
4137
+ //
4138
+ list: () => Promise.resolve().then(function () { return entityMasterListView_component; }).then((c) => c.AXPEntityMasterListViewComponent),
4139
+ },
4140
+ },
4141
+ };
4142
+ return config;
4143
+ },
4144
+ },
4145
+ { provide: RouteReuseStrategy, useClass: AXPEntityReuseStrategy },
4146
+ {
4147
+ provide: ROUTES,
4148
+ multi: true,
4149
+ useFactory: routesFacory,
4150
+ },
4151
+ ],
4152
+ }]
4153
+ }] });
4154
+
4155
+ /**
4156
+ * Generated bundle index. Do not edit.
4157
+ */
4158
+
4159
+ export { AXPDefaultThemeModule, AXPEntityDetailListViewComponent, AXPEntityMasterListViewComponent, AXPRootHorizontalLayoutComponent, AXPRootLayoutComponent, AXPRootLayoutFooterComponent, AXPRootLayoutHeaderComponent, AXPRootLayoutMenuComponent, AXPRootVerticalLayoutComponent, AXPRouteTabBarComponent };
4160
+ //# sourceMappingURL=acorex-platform-framework-client-themes-default.mjs.map