@acorex/platform 21.0.0-next.85 → 21.0.0-next.88

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 (74) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +1 -1
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-VfmZct9H.mjs} +43 -3
  4. package/fesm2022/acorex-platform-common-common-settings.provider-VfmZct9H.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-common.mjs +1709 -870
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-contracts.mjs +1160 -9
  8. package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-core.mjs +210 -131
  10. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-domain-contracts.mjs +58 -0
  12. package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-domain.mjs +2 -50
  14. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-components.mjs +59 -23
  17. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-designer.mjs +3 -2
  19. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  20. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-entity-contracts.mjs +1114 -0
  22. package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-entity.mjs +661 -742
  25. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  26. package/fesm2022/acorex-platform-layout-views.mjs +333 -119
  27. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  28. package/fesm2022/acorex-platform-layout-widget-core.mjs +11 -105
  29. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  30. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
  31. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
  32. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs} +4 -4
  33. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs.map +1 -0
  34. package/fesm2022/acorex-platform-layout-widgets.mjs +868 -57
  35. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  36. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  37. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs → acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs} +2 -3
  38. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-themes-default.mjs +740 -96
  40. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  41. package/fesm2022/{acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs → acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs} +3 -3
  42. package/fesm2022/acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-themes-shared.mjs +4 -3
  44. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  45. package/fesm2022/acorex-platform-workflow.mjs +6 -8
  46. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  47. package/package.json +9 -5
  48. package/types/acorex-platform-auth.d.ts +2 -1
  49. package/types/acorex-platform-common.d.ts +419 -1083
  50. package/types/acorex-platform-contracts.d.ts +1791 -260
  51. package/types/acorex-platform-core.d.ts +2153 -2806
  52. package/types/acorex-platform-domain-contracts.d.ts +416 -0
  53. package/types/acorex-platform-domain.d.ts +4 -415
  54. package/types/acorex-platform-layout-builder.d.ts +2 -1
  55. package/types/acorex-platform-layout-components.d.ts +15 -4
  56. package/types/acorex-platform-layout-designer.d.ts +2 -1
  57. package/types/acorex-platform-layout-entity-contracts.d.ts +864 -0
  58. package/types/acorex-platform-layout-entity.d.ts +88 -269
  59. package/types/acorex-platform-layout-views.d.ts +48 -14
  60. package/types/acorex-platform-layout-widget-core.d.ts +9 -5
  61. package/types/acorex-platform-layout-widgets.d.ts +89 -13
  62. package/types/acorex-platform-runtime.d.ts +1 -1
  63. package/types/acorex-platform-themes-default.d.ts +173 -70
  64. package/types/acorex-platform-workflow.d.ts +5 -18
  65. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
  66. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
  67. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
  68. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +0 -1
  69. package/fesm2022/acorex-platform-native.mjs +0 -155
  70. package/fesm2022/acorex-platform-native.mjs.map +0 -1
  71. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
  72. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +0 -1
  73. package/native/README.md +0 -4
  74. package/types/acorex-platform-native.d.ts +0 -28
@@ -1,19 +1,22 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, Injector, signal, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, output, ViewEncapsulation, ChangeDetectionStrategy, Component, viewChild, ErrorHandler, EventEmitter, Input, Output, Directive, Inject, runInInjectionContext, model, linkedSignal, afterNextRender } from '@angular/core';
3
- import { kebabCase, merge, sortBy, cloneDeep, get, isNumber, isPlainObject, omit } from 'lodash-es';
2
+ import { InjectionToken, inject, Injectable, Injector, signal, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, output, ViewEncapsulation, ChangeDetectionStrategy, Component, viewChild, ErrorHandler, EventEmitter, Input, Output, Directive, effect, Inject, runInInjectionContext, model, linkedSignal, afterNextRender } from '@angular/core';
3
+ import { normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, resolveEntityModifyRecordTitleTemplate, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, ENTITY_LAYOUT_SINGLE_TITLE_PATH, buildEntitySearchTitleContext, resolveEntityRowTitleTemplate } from '@acorex/platform/layout/entity-contracts';
4
+ export * from '@acorex/platform/layout/entity-contracts';
5
+ export { ENTITY_LAYOUT_SINGLE_TITLE_PATH, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, buildEntitySearchTitleContext, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, normalizeEntityDisplayTemplate, resolveEntityModifyRecordTitleTemplate, resolveEntityRowTitleTemplate, sanitizeResolvedEntityDisplayTitle } from '@acorex/platform/layout/entity-contracts';
4
6
  import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, RouterModule } from '@angular/router';
5
7
  import { AXPSessionService, AXPSessionStatus, AXPAuthGuard } from '@acorex/platform/auth';
6
8
  import { Subject, distinctUntilChanged, merge as merge$1, first, switchMap, from, map } from 'rxjs';
7
- import * as i2$1 from '@acorex/platform/core';
8
- import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, AXPBroadcastEventService, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, AXPSystemActionType, isLocaleStringMap, AXPExpressionEvaluatorService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
9
+ import { AXPPlatformScope, AXPRegionalSetting, AXPWidgetsCatalog, AXPDataGenerator, AXPSystemActionType } from '@acorex/platform/contracts';
10
+ import * as i3$1 from '@acorex/platform/core';
11
+ import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPBroadcastEventService, objectKeyValueTransforms, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, isLocaleStringMap, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPDeviceService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
12
+ import { merge, sortBy, cloneDeep, get, isNumber, isNil, isObject, isString, isPlainObject, omit, defaultTo } from 'lodash-es';
9
13
  import { AXCalendarService, AXDateTimeModule } from '@acorex/core/date-time';
10
14
  import { AXDialogService } from '@acorex/components/dialog';
11
15
  import * as i5 from '@acorex/core/translation';
12
- import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
16
+ import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString } from '@acorex/core/translation';
13
17
  import * as i4$1 from '@angular/common';
14
18
  import { Location, CommonModule, AsyncPipe, DOCUMENT } from '@angular/common';
15
19
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
16
- import { AXPWidgetsCatalog } from '@acorex/platform/layout/widget-core';
17
20
  import * as i4 from '@acorex/components/decorators';
18
21
  import { AXDecoratorModule } from '@acorex/components/decorators';
19
22
  import * as i2 from '@acorex/components/popover';
@@ -24,13 +27,15 @@ import * as i3 from '@acorex/components/progress-bar';
24
27
  import { AXProgressBarModule } from '@acorex/components/progress-bar';
25
28
  import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
26
29
  import { AXToastService, AXToastModule } from '@acorex/components/toast';
27
- import * as i3$1 from '@acorex/platform/workflow';
30
+ import * as i2$1 from '@acorex/core/format';
31
+ import { AXFormatService } from '@acorex/core/format';
32
+ import * as i4$2 from '@acorex/platform/workflow';
28
33
  import { AXPWorkflowService, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowError, AXPWorkflowModule } from '@acorex/platform/workflow';
29
34
  import { AXKBDComponent, AXKBDItemComponent } from '@acorex/components/kbd';
30
35
  import { AXDataSource } from '@acorex/cdk/common';
31
36
  import { AXPCommandExecutor, AXPCommandService } from '@acorex/platform/runtime';
32
37
  import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
33
- import { AXFormatService } from '@acorex/core/format';
38
+ import { filter } from 'rxjs/operators';
34
39
  import { AXFileService } from '@acorex/core/file';
35
40
  import { AXCheckBoxModule } from '@acorex/components/check-box';
36
41
  import * as i6 from '@acorex/components/color-box';
@@ -164,110 +169,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
164
169
  }]
165
170
  }] });
166
171
 
167
- var AXPRelationshipKind;
168
- (function (AXPRelationshipKind) {
169
- AXPRelationshipKind[AXPRelationshipKind["Association"] = 0] = "Association";
170
- AXPRelationshipKind[AXPRelationshipKind["Composition"] = 1] = "Composition";
171
- AXPRelationshipKind[AXPRelationshipKind["Aggregation"] = 2] = "Aggregation";
172
- })(AXPRelationshipKind || (AXPRelationshipKind = {}));
173
- var AXPRelationshipCardinality;
174
- (function (AXPRelationshipCardinality) {
175
- AXPRelationshipCardinality[AXPRelationshipCardinality["OneToOne"] = 0] = "OneToOne";
176
- AXPRelationshipCardinality[AXPRelationshipCardinality["OneToMany"] = 1] = "OneToMany";
177
- AXPRelationshipCardinality[AXPRelationshipCardinality["ManyToMany"] = 2] = "ManyToMany";
178
- })(AXPRelationshipCardinality || (AXPRelationshipCardinality = {}));
179
- var AXPEntityCommandScope;
180
- (function (AXPEntityCommandScope) {
181
- AXPEntityCommandScope["TypeLevel"] = "typeLevel";
182
- AXPEntityCommandScope["Selected"] = "selected";
183
- AXPEntityCommandScope["Individual"] = "individual";
184
- AXPEntityCommandScope["Section"] = "section";
185
- })(AXPEntityCommandScope || (AXPEntityCommandScope = {}));
186
- var AXPEntityQueryType;
187
- (function (AXPEntityQueryType) {
188
- AXPEntityQueryType["Single"] = "single";
189
- AXPEntityQueryType["List"] = "list";
190
- })(AXPEntityQueryType || (AXPEntityQueryType = {}));
191
- /** Whether the card field is shown as a header badge (not in the card body). */
192
- function isCardFieldBadgeDisplay(display) {
193
- if (display === 'badge') {
194
- return true;
195
- }
196
- return typeof display === 'object' && display !== null && display.type === 'badge';
197
- }
198
- /** Resolves static badge color config from {@link AXPCardField.display} and legacy {@link AXPCardField.badgeColor}. */
199
- function resolveCardFieldBadgeColor(field) {
200
- if (typeof field.display === 'object' && field.display.type === 'badge') {
201
- return field.display.options?.color ?? field.badgeColor;
202
- }
203
- if (field.display === 'badge') {
204
- return field.badgeColor;
205
- }
206
- return undefined;
207
- }
208
- function getEntityInfo(entity) {
209
- return {
210
- source: `${entity.module}.${entity.name}`,
211
- module: {
212
- route: kebabCase(entity.module),
213
- },
214
- entity: {
215
- route: kebabCase(entity.name),
216
- },
217
- };
218
- }
219
- function createQueryView(name, title, fixed, params = {
220
- columns: [],
221
- conditions: [],
222
- sorts: [],
223
- }) {
224
- params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
225
- return {
226
- name,
227
- title,
228
- fixed,
229
- columns: params.columns ?? [],
230
- conditions: params.conditions ?? [],
231
- sorts: params.sorts ?? [],
232
- };
233
- }
234
- function createAllQueryView(params = {
235
- columns: [],
236
- sorts: [],
237
- conditions: [],
238
- }) {
239
- params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
240
- return createQueryView('all', '@general:terms.interface.selection.all-items', true, {
241
- columns: params.columns ?? [],
242
- conditions: params.conditions ?? [],
243
- sorts: params.sorts ?? [],
244
- });
245
- }
246
- /** Resolves master list layouts that are currently enabled. */
247
- function resolveEnabledMasterListLayouts(layouts) {
248
- if (!layouts) {
249
- return ['table'];
250
- }
251
- const enabled = [];
252
- if (layouts.table?.enabled !== false) {
253
- enabled.push('table');
254
- }
255
- if (layouts.card && layouts.card.enabled !== false) {
256
- enabled.push('card');
257
- }
258
- return enabled.length > 0 ? enabled : ['table'];
259
- }
260
- /** First enabled layout; used as the default when no user preference is stored. */
261
- function resolveDefaultMasterListLayout(layouts) {
262
- return resolveEnabledMasterListLayouts(layouts)[0];
263
- }
264
- // new version
265
- var AXPEntityType;
266
- (function (AXPEntityType) {
267
- AXPEntityType[AXPEntityType["Entity"] = 0] = "Entity";
268
- AXPEntityType[AXPEntityType["AggregateRoot"] = 1] = "AggregateRoot";
269
- AXPEntityType[AXPEntityType["ValueObject"] = 2] = "ValueObject";
270
- })(AXPEntityType || (AXPEntityType = {}));
172
+ /**
173
+ * Entity definition types, query views, and related helpers.
174
+ * Canonical definitions live in @acorex/platform/layout/entity-contracts.
175
+ */
271
176
 
272
177
  const EQ_OPER = { name: 'equal', title: '@general:terms.operators.equal', hasValue: true };
273
178
  const NOT_EQ_OPER = { name: 'notEqual', title: '@general:terms.operators.not-equal', hasValue: true };
@@ -472,38 +377,6 @@ const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY'
472
377
  factory: () => 'home'
473
378
  });
474
379
 
475
- /**
476
- * Regional and locale-related setting keys used by platform widgets and components.
477
- * Most values use the `LocaleManagement:Setting:Regional.*` prefix; definitions live in the locale-management module where applicable.
478
- */
479
- var AXPRegionalSetting;
480
- (function (AXPRegionalSetting) {
481
- AXPRegionalSetting["LocaleProfile"] = "LocaleManagement:Setting:Regional.LocaleProfile";
482
- AXPRegionalSetting["TimeZone"] = "LocaleManagement:Setting:Regional.TimeZone";
483
- AXPRegionalSetting["Language"] = "LocaleManagement:Setting:Regional.Language";
484
- AXPRegionalSetting["Country"] = "LocaleManagement:Setting:Regional.Country";
485
- AXPRegionalSetting["Calendar"] = "LocaleManagement:Setting:Regional.Calendar";
486
- /** User-selected holiday calendars shown on the task board and other calendar views. */
487
- AXPRegionalSetting["CalendarEvents"] = "LocaleManagement:Setting:Regional.CalendarEvents";
488
- AXPRegionalSetting["FirstDayOfWeek"] = "LocaleManagement:Setting:Regional.FirstDayOfWeek";
489
- AXPRegionalSetting["WeekendDays"] = "LocaleManagement:Setting:Regional.WeekendDays";
490
- AXPRegionalSetting["ShortDate"] = "LocaleManagement:Setting:Regional.ShortDate";
491
- AXPRegionalSetting["MediumDate"] = "LocaleManagement:Setting:Regional.MediumDate";
492
- AXPRegionalSetting["LongDate"] = "LocaleManagement:Setting:Regional.LongDate";
493
- AXPRegionalSetting["ShortTime"] = "LocaleManagement:Setting:Regional.ShortTime";
494
- AXPRegionalSetting["MediumTime"] = "LocaleManagement:Setting:Regional.MediumTime";
495
- AXPRegionalSetting["LongTime"] = "LocaleManagement:Setting:Regional.LongTime";
496
- AXPRegionalSetting["MeasurementUnits"] = "LocaleManagement:Setting:Regional.MeasurementUnits";
497
- AXPRegionalSetting["TemperatureUnits"] = "LocaleManagement:Setting:Regional.TemperatureUnits";
498
- AXPRegionalSetting["DistanceUnits"] = "LocaleManagement:Setting:Regional.DistanceUnits";
499
- AXPRegionalSetting["WeightUnits"] = "LocaleManagement:Setting:Regional.WeightUnits";
500
- AXPRegionalSetting["VolumeUnits"] = "LocaleManagement:Setting:Regional.VolumeUnits";
501
- AXPRegionalSetting["SpeedUnits"] = "LocaleManagement:Setting:Regional.SpeedUnits";
502
- AXPRegionalSetting["AreaUnits"] = "LocaleManagement:Setting:Regional.AreaUnits";
503
- /** Default multi-language behavior for standard text field names; defined in locale-management `AXMRegionalSettingProvider`. */
504
- AXPRegionalSetting["MultiLanguageSupport"] = "LocaleManagement:Setting:Regional.MultiLanguageSupport";
505
- })(AXPRegionalSetting || (AXPRegionalSetting = {}));
506
-
507
380
  class AXPSettingDefinitionProviderContext {
508
381
  constructor() {
509
382
  this.rootGroups = [];
@@ -601,6 +474,7 @@ class AXPSettingDefinitionSectionBuilder {
601
474
  isRequired: setting.isRequired ?? false,
602
475
  isInherited: setting.isInherited ?? false,
603
476
  isEncrypted: setting.isEncrypted ?? false,
477
+ requiresPageRefresh: setting.requiresPageRefresh ?? false,
604
478
  defaultValue: setting.defaultValue,
605
479
  scope: setting.scope ?? 'P',
606
480
  layout: setting.widget.layout,
@@ -735,6 +609,26 @@ class AXPSettingDefinitionProviderService {
735
609
  collect(this.cache);
736
610
  return scopes;
737
611
  }
612
+ async requiresPageRefreshByKey() {
613
+ const refreshByKey = {};
614
+ const collect = (groups) => {
615
+ groups.forEach((group) => {
616
+ group.sections.forEach((section) => {
617
+ section.settings.forEach((setting) => {
618
+ if (setting.requiresPageRefresh) {
619
+ refreshByKey[setting.name] = true;
620
+ }
621
+ });
622
+ });
623
+ collect(group.groups);
624
+ });
625
+ };
626
+ if (!this.cache) {
627
+ await this.load();
628
+ }
629
+ collect(this.cache);
630
+ return refreshByKey;
631
+ }
738
632
  findGroup(scope, groupName) {
739
633
  return this.searchRecursive(this.getList(scope), groupName, []); // Initialize with an empty breadcrumb
740
634
  }
@@ -1425,6 +1319,8 @@ var AXPCommonSettings;
1425
1319
  AXPCommonSettings["DebugMode"] = "PlatformDevTools:Setting:Developer.DebugMode";
1426
1320
  AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
1427
1321
  AXPCommonSettings["EntityListPersistenceMode"] = "Common:Setting:Entity.ListPersistenceMode";
1322
+ AXPCommonSettings["NavigationMode"] = "Common:Setting:Navigation.Mode";
1323
+ AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
1428
1324
  })(AXPCommonSettings || (AXPCommonSettings = {}));
1429
1325
 
1430
1326
  //#endregion
@@ -1611,9 +1507,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1611
1507
  }]
1612
1508
  }], ctorParameters: () => [] });
1613
1509
 
1510
+ //#endregion
1511
+ /**
1512
+ * Holds detached Angular route component instances keyed by tab key so tabular navigation can
1513
+ * preserve a page's full state (data-table selection, scroll, unsaved form values, etc.) while it
1514
+ * is in the background, then reattach it without re-rendering.
1515
+ *
1516
+ * The registry has no `Router` dependency on purpose: it is consumed by the {@link RouteReuseStrategy}
1517
+ * implementation, so depending on `Router` here would create a construction-time cycle.
1518
+ */
1519
+ class AXPTabRouteCacheRegistry {
1520
+ constructor() {
1521
+ //#region ---- State ----
1522
+ this.handles = new Map();
1523
+ this.openKeys = new Set();
1524
+ this.enabled = false;
1525
+ this.deactivateGuardSuspended = false;
1526
+ }
1527
+ //#endregion
1528
+ //#region ---- Deactivate Guard Suspension ----
1529
+ /**
1530
+ * Runs `action` while unsaved-changes `canDeactivate` guards are suspended, so switching between
1531
+ * tabs preserves a dirty page without prompting. Closing a tab does not use this path, so the
1532
+ * prompt still appears there.
1533
+ */
1534
+ async runWithSuspendedDeactivateGuards(action) {
1535
+ const previous = this.deactivateGuardSuspended;
1536
+ this.deactivateGuardSuspended = true;
1537
+ try {
1538
+ return await action();
1539
+ }
1540
+ finally {
1541
+ this.deactivateGuardSuspended = previous;
1542
+ }
1543
+ }
1544
+ isDeactivateGuardSuspended() {
1545
+ return this.deactivateGuardSuspended;
1546
+ }
1547
+ //#endregion
1548
+ //#region ---- Mode ----
1549
+ /** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
1550
+ setEnabled(enabled) {
1551
+ if (this.enabled === enabled) {
1552
+ return;
1553
+ }
1554
+ this.enabled = enabled;
1555
+ if (!enabled) {
1556
+ this.clear();
1557
+ }
1558
+ }
1559
+ isEnabled() {
1560
+ return this.enabled;
1561
+ }
1562
+ //#endregion
1563
+ //#region ---- Open Keys Sync ----
1564
+ /**
1565
+ * Reconciles open scoped tab keys and active tab URLs. Cached handles whose key is not an open
1566
+ * scoped tab key, an active tab URL, or a child path under an open scoped tab are destroyed.
1567
+ */
1568
+ syncOpenKeys(scopedKeys, activeTabUrls = []) {
1569
+ const scopedSet = new Set(scopedKeys);
1570
+ const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
1571
+ for (const cacheKey of [...this.handles.keys()]) {
1572
+ const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
1573
+ const keepsScopedKey = scopedSet.has(normalizedCacheKey);
1574
+ const keepsActiveUrl = activePathSet.has(normalizedCacheKey);
1575
+ const keepsUnderScopedTab = [...scopedSet].some((scopedKey) => normalizedCacheKey === scopedKey || normalizedCacheKey.startsWith(`${scopedKey}/`));
1576
+ if (!keepsScopedKey && !keepsActiveUrl && !keepsUnderScopedTab) {
1577
+ this.destroyHandle(this.handles.get(cacheKey));
1578
+ this.handles.delete(cacheKey);
1579
+ }
1580
+ }
1581
+ this.openKeys = scopedSet;
1582
+ }
1583
+ isOpen(key) {
1584
+ return this.openKeys.has(key);
1585
+ }
1586
+ /**
1587
+ * Returns the live component instance for a cached background tab, when available.
1588
+ * Matches the tab key directly or any cached handle under a scoped tab prefix.
1589
+ */
1590
+ getComponentInstance(key) {
1591
+ const normalizedKey = key.replace(/\/+$/, '') || '/';
1592
+ const direct = this.handles.get(normalizedKey) ?? this.handles.get(key);
1593
+ if (direct) {
1594
+ return this.readComponentInstance(direct);
1595
+ }
1596
+ for (const [cacheKey, handle] of this.handles) {
1597
+ const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
1598
+ if (normalizedCacheKey === normalizedKey || normalizedCacheKey.startsWith(`${normalizedKey}/`)) {
1599
+ return this.readComponentInstance(handle);
1600
+ }
1601
+ }
1602
+ return null;
1603
+ }
1604
+ //#endregion
1605
+ //#region ---- Handle Store ----
1606
+ /**
1607
+ * Caches a detached background handle. Overwrites without destroying the previous handle: on a
1608
+ * repeat visit Angular hands back a new handle object wrapping the same live `componentRef`, so
1609
+ * destroying the old one would destroy the live component and corrupt the view (NG0922).
1610
+ */
1611
+ store(key, handle) {
1612
+ this.handles.set(key, handle);
1613
+ }
1614
+ /**
1615
+ * Returns the cached handle without mutating the map. Angular reads a handle twice per
1616
+ * navigation (once while building the future state, once while attaching), so `retrieve` must be
1617
+ * idempotent; the handle is released afterwards via {@link release} when Angular calls
1618
+ * `store(snapshot, null)`.
1619
+ */
1620
+ retrieve(key) {
1621
+ return this.handles.get(key) ?? null;
1622
+ }
1623
+ /**
1624
+ * Drops a handle that the router has just reattached (it is now a live foreground view, so it
1625
+ * must not be destroyed). The map keeps only truly backgrounded handles.
1626
+ */
1627
+ release(key) {
1628
+ this.handles.delete(key);
1629
+ }
1630
+ has(key) {
1631
+ return this.handles.has(key);
1632
+ }
1633
+ /** Destroys and removes a single cached handle (used when a tab closes). */
1634
+ evict(key) {
1635
+ this.destroyHandle(this.handles.get(key));
1636
+ this.handles.delete(key);
1637
+ }
1638
+ /** Destroys and removes every cached handle. */
1639
+ clear() {
1640
+ for (const handle of this.handles.values()) {
1641
+ this.destroyHandle(handle);
1642
+ }
1643
+ this.handles.clear();
1644
+ this.openKeys.clear();
1645
+ }
1646
+ //#endregion
1647
+ //#region ---- Utility Methods ----
1648
+ readComponentInstance(handle) {
1649
+ const componentRef = handle.componentRef;
1650
+ return componentRef?.instance ?? null;
1651
+ }
1652
+ destroyHandle(handle) {
1653
+ if (!handle) {
1654
+ return;
1655
+ }
1656
+ const componentRef = handle.componentRef;
1657
+ componentRef?.destroy?.();
1658
+ }
1659
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1660
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, providedIn: 'root' }); }
1661
+ }
1662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, decorators: [{
1663
+ type: Injectable,
1664
+ args: [{
1665
+ providedIn: 'root',
1666
+ }]
1667
+ }] });
1668
+
1614
1669
  //#region ---- Unsaved Changes Can Deactivate Guard Factory ----
1615
1670
  function createUnsavedChangesCanDeactivateGuard() {
1616
- return (component) => component.canDeactivate();
1671
+ return (component) => {
1672
+ // Tab switches keep the page alive in the background, so skip the prompt; closing a tab does not
1673
+ // suspend guards and still prompts.
1674
+ const tabCache = inject(AXPTabRouteCacheRegistry, { optional: true });
1675
+ if (tabCache?.isDeactivateGuardSuspended()) {
1676
+ return true;
1677
+ }
1678
+ return component.canDeactivate();
1679
+ };
1617
1680
  }
1618
1681
  //#endregion
1619
1682
 
@@ -1670,7 +1733,7 @@ class AXPHomePageService {
1670
1733
  return [
1671
1734
  { path: '', redirectTo: fullPath, pathMatch: 'full' },
1672
1735
  { path: ':app', redirectTo: fullPath, pathMatch: 'full' },
1673
- { ...home?.route, ...{ data: { key: home?.key, title: home?.title, path: fullPath } } },
1736
+ { ...home?.route, data: { ...home?.route?.data, key: home?.key, title: home?.title, path: fullPath } },
1674
1737
  ];
1675
1738
  }
1676
1739
  return [{ path: '', redirectTo: this.loginPath, pathMatch: 'full' }];
@@ -2991,6 +3054,56 @@ function axpRedirectToUnauthorized(router = inject(Router)) {
2991
3054
  return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
2992
3055
  }
2993
3056
 
3057
+ //#endregion
3058
+ /**
3059
+ * Platform formatter for display text: resolves ML objects, i18n keys (`@scope:key`), and primitives.
3060
+ * Register via {@link AXPCommonModule} (auto) or `AXFormatModule.forRoot({ formatters: [AXPTranslateFormatter] })`.
3061
+ */
3062
+ class AXPTranslateFormatter {
3063
+ constructor() {
3064
+ //#region ---- Services & Dependencies ----
3065
+ this.translationService = inject(AXTranslationService);
3066
+ }
3067
+ //#endregion
3068
+ //#region ---- AXFormatter ----
3069
+ get name() {
3070
+ return 'translate';
3071
+ }
3072
+ format(value, options) {
3073
+ if (isNil(value) || value === '') {
3074
+ return '';
3075
+ }
3076
+ if (isObject(value) && !Array.isArray(value) && !(value instanceof Date)) {
3077
+ const resolved = this.translationService.resolve(value);
3078
+ return resolved?.trim() ?? '';
3079
+ }
3080
+ if (isString(value)) {
3081
+ const text = value.trim();
3082
+ if (!text) {
3083
+ return '';
3084
+ }
3085
+ if (text.startsWith('@')) {
3086
+ const translateOptions = typeof options === 'object' ? options : undefined;
3087
+ const translated = translateSync(text, translateOptions);
3088
+ return translated?.trim() ?? text;
3089
+ }
3090
+ return text;
3091
+ }
3092
+ if (typeof value === 'number' || typeof value === 'boolean') {
3093
+ return String(value);
3094
+ }
3095
+ if (value instanceof Date) {
3096
+ return value.toISOString();
3097
+ }
3098
+ return String(value).trim();
3099
+ }
3100
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3101
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter }); }
3102
+ }
3103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, decorators: [{
3104
+ type: Injectable
3105
+ }] });
3106
+
2994
3107
  class AXPFileStorageService {
2995
3108
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2996
3109
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService }); }
@@ -4429,7 +4542,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4429
4542
  }]
4430
4543
  }] });
4431
4544
 
4545
+ //#endregion
4546
+ //#region ---- App Path Navigation ----
4547
+ /**
4548
+ * Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
4549
+ * Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
4550
+ */
4551
+ function axpParseAppPath(path) {
4552
+ const trimmed = path.trim();
4553
+ const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
4554
+ const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
4555
+ const [pathPart, fragment] = pathAndFragment.split('#');
4556
+ const segments = pathPart.split('/').filter(Boolean);
4557
+ const queryParams = queryString
4558
+ ? queryString.split('&').reduce((params, pair) => {
4559
+ const [key, value = ''] = pair.split('=');
4560
+ if (key) {
4561
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
4562
+ }
4563
+ return params;
4564
+ }, {})
4565
+ : undefined;
4566
+ return {
4567
+ segments,
4568
+ queryParams,
4569
+ fragment: fragment || undefined,
4570
+ };
4571
+ }
4572
+ /** Navigates using explicit path segments (same pattern as entity preload filters). */
4573
+ function axpNavigateAppPath(router, path, extras) {
4574
+ const { segments, queryParams, fragment } = axpParseAppPath(path);
4575
+ return router.navigate(segments, {
4576
+ ...extras,
4577
+ queryParams,
4578
+ fragment,
4579
+ });
4580
+ }
4581
+ /** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
4582
+ function axpIsEntityListPath(url) {
4583
+ const path = url.split('?')[0].split('#')[0];
4584
+ return /\/e\/[^/]+\/list$/.test(path);
4585
+ }
4586
+ /** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
4587
+ function axpIsEntityListRoute(router) {
4588
+ return axpIsEntityListPath(router.url);
4589
+ }
4590
+ /** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
4591
+ function axpIsEntityDetailsPath(url) {
4592
+ const path = url.split('?')[0].split('#')[0];
4593
+ return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
4594
+ }
4595
+ /** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
4596
+ function axpIsEntityDetailsViewRoute(router) {
4597
+ return axpIsEntityDetailsPath(router.url);
4598
+ }
4599
+ /**
4600
+ * Merges query params on the current entity list route without pushing history.
4601
+ * Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
4602
+ */
4603
+ function axpReplaceEntityListRouteQueryParams(router, queryParams) {
4604
+ const url = router.url;
4605
+ if (!axpIsEntityListPath(url)) {
4606
+ return;
4607
+ }
4608
+ const pathOnly = url.split('?')[0].split('#')[0];
4609
+ const { segments } = axpParseAppPath(pathOnly);
4610
+ void router.navigate(segments, {
4611
+ queryParams,
4612
+ queryParamsHandling: 'merge',
4613
+ replaceUrl: true,
4614
+ });
4615
+ }
4616
+ //#endregion
4617
+
4432
4618
  const DEFAULT_TRAILING_SEGMENTS = ['list', 'view', 'edit', 'create'];
4619
+ //#region ---- Entity Route Helpers ----
4620
+ /**
4621
+ * Parses `/{app}/m/{module}/e/{entity}/...` routes into a stable entity key.
4622
+ */
4623
+ function axpParseEntityRouteKey(segments) {
4624
+ const mIndex = segments.indexOf('m');
4625
+ const eIndex = segments.indexOf('e');
4626
+ if (mIndex < 1 || eIndex <= mIndex + 1 || eIndex + 1 >= segments.length) {
4627
+ return null;
4628
+ }
4629
+ return {
4630
+ app: segments[mIndex - 1],
4631
+ module: segments[mIndex + 1],
4632
+ entity: segments[eIndex + 1],
4633
+ entityEndIndex: eIndex + 2,
4634
+ };
4635
+ }
4636
+ function axpEntityRouteKeysEqual(left, right) {
4637
+ return left.app === right.app && left.module === right.module && left.entity === right.entity;
4638
+ }
4639
+ /**
4640
+ * Matches entity list menu entries against list/detail/create/edit routes for the same entity.
4641
+ */
4642
+ function axpTryMatchEntityMenuRoute(itemSegments, pathSegments, currentBase) {
4643
+ const menuEntity = axpParseEntityRouteKey(itemSegments);
4644
+ const currentEntity = axpParseEntityRouteKey(pathSegments);
4645
+ if (!menuEntity || !currentEntity || !axpEntityRouteKeysEqual(menuEntity, currentEntity)) {
4646
+ return null;
4647
+ }
4648
+ const isPartial = pathSegments.length > menuEntity.entityEndIndex ||
4649
+ axpIsEntityDetailsPath(currentBase) ||
4650
+ !axpIsEntityListPath(currentBase);
4651
+ return { isMatch: true, isPartial };
4652
+ }
4653
+ //#endregion
4433
4654
  /**
4434
4655
  * Compares a menu item path with the current browser path.
4435
4656
  * Mirrors the matching rules used when selecting the active menu item.
@@ -4438,9 +4659,13 @@ function axpMenuPathsMatch(itemPath, currentPath, trailingSegmentsToIgnore = DEF
4438
4659
  const itemNoHash = itemPath.split('#')[0];
4439
4660
  const currentNoHash = currentPath.split('#')[0];
4440
4661
  const [itemBase, itemQueryString = ''] = itemNoHash.split('?');
4441
- const [currentBase, currentQueryString = ''] = currentNoHash.split('?');
4662
+ const [currentBase] = currentNoHash.split('?');
4442
4663
  const itemSegments = itemBase.split('/').filter((segment) => segment.length > 0);
4443
4664
  const pathSegments = currentBase.split('/').filter((segment) => segment.length > 0);
4665
+ const entityMatch = axpTryMatchEntityMenuRoute(itemSegments, pathSegments, `/${currentBase}`);
4666
+ if (entityMatch) {
4667
+ return entityMatch;
4668
+ }
4444
4669
  const menuLength = itemSegments.length;
4445
4670
  const pathLength = pathSegments.length;
4446
4671
  let effectiveMenuLength = menuLength;
@@ -4463,7 +4688,7 @@ function axpMenuPathsMatch(itemPath, currentPath, trailingSegmentsToIgnore = DEF
4463
4688
  const isPartialMatch = effectiveMenuLength < pathLength;
4464
4689
  const isExactMatch = effectiveMenuLength === pathLength;
4465
4690
  const itemParams = new URLSearchParams(itemQueryString);
4466
- const currentParams = new URLSearchParams(currentQueryString);
4691
+ const currentParams = new URLSearchParams(currentNoHash.split('?')[1] ?? '');
4467
4692
  let queryMatches = true;
4468
4693
  const itemParamPairs = [];
4469
4694
  itemParams.forEach((value, key) => {
@@ -4508,6 +4733,10 @@ function axpFindMenuItemByRoute(items, currentPath, trailingSegmentsToIgnore = D
4508
4733
  trailingSegmentsToIgnore.includes(itemSegments[itemSegments.length - 1])) {
4509
4734
  effectiveMenuLength--;
4510
4735
  }
4736
+ const menuEntity = axpParseEntityRouteKey(itemSegments);
4737
+ if (menuEntity) {
4738
+ effectiveMenuLength = menuEntity.entityEndIndex;
4739
+ }
4511
4740
  if (effectiveMenuLength > bestEffectiveLength) {
4512
4741
  bestEffectiveLength = effectiveMenuLength;
4513
4742
  bestMatch = { item, ancestors, isPartialMatch: matchResult.isPartial };
@@ -4536,29 +4765,13 @@ withMethods((store, router = inject(Router), workflow = inject(AXPWorkflowServic
4536
4765
  patchState(store, { items: items });
4537
4766
  },
4538
4767
  selectMenuItemByRoute(path) {
4539
- const currentRawPath = path;
4540
- const findItem = (items) => {
4541
- for (const item of items) {
4542
- // Recursively search children first
4543
- if (item.children) {
4544
- const foundChild = findItem(item.children);
4545
- if (foundChild.item) {
4546
- return foundChild;
4547
- }
4548
- }
4549
- if (item.path) {
4550
- // Check if the item path matches the current path (including query param subset if provided on item)
4551
- const matchResult = axpMenuPathsMatch(item.path, currentRawPath, ['list', 'view', 'edit', 'create']);
4552
- if (matchResult.isMatch) {
4553
- return { item, isPartialMatch: matchResult.isPartial };
4554
- }
4555
- }
4556
- }
4557
- return { item: null, isPartialMatch: false };
4558
- };
4559
- const items = store.items();
4560
- const { item, isPartialMatch } = findItem(items);
4561
- patchState(store, { selectedMenuItem: { item, isFullMatch: !isPartialMatch } });
4768
+ const match = axpFindMenuItemByRoute(store.items(), path, ['list', 'view', 'edit', 'create']);
4769
+ patchState(store, {
4770
+ selectedMenuItem: {
4771
+ item: match?.item ?? null,
4772
+ isFullMatch: match ? !match.isPartialMatch : false,
4773
+ },
4774
+ });
4562
4775
  },
4563
4776
  executeCommand(item) {
4564
4777
  if (item.path) {
@@ -4579,24 +4792,28 @@ withMethods((store, router = inject(Router), workflow = inject(AXPWorkflowServic
4579
4792
  }
4580
4793
  },
4581
4794
  isItemOpen(item) {
4582
- const selectedItem = store.selectedMenuItem();
4583
- if (!selectedItem.item) {
4795
+ const selectedItem = store.selectedMenuItem().item;
4796
+ if (!selectedItem) {
4584
4797
  return false;
4585
4798
  }
4799
+ if (item === selectedItem || (item.name && item.name === selectedItem.name)) {
4800
+ return true;
4801
+ }
4586
4802
  const findParent = (currentItem, targetItem) => {
4587
- if (currentItem.children?.includes(targetItem)) {
4803
+ if (currentItem.children?.some((child) => child === targetItem || (child.name && child.name === targetItem.name))) {
4588
4804
  return true;
4589
4805
  }
4590
4806
  return currentItem.children?.some((child) => findParent(child, targetItem)) ?? false;
4591
4807
  };
4592
- return item === selectedItem.item || findParent(item, selectedItem.item);
4808
+ return findParent(item, selectedItem);
4593
4809
  },
4594
4810
  };
4595
- }), withHooks((store, menuProviderService = inject(AXPMenuProviderService), menuKeyboardShortcuts = inject(AXPMenuKeyboardShortcutService)) => {
4811
+ }), withHooks((store, menuProviderService = inject(AXPMenuProviderService), menuKeyboardShortcuts = inject(AXPMenuKeyboardShortcutService), router = inject(Router)) => {
4596
4812
  let reloadSubscription;
4597
4813
  const applyMenuItems = (items) => {
4598
4814
  patchState(store, { items });
4599
4815
  menuKeyboardShortcuts.sync(items);
4816
+ store.selectMenuItemByRoute(router.url);
4600
4817
  };
4601
4818
  return {
4602
4819
  onInit() {
@@ -4756,6 +4973,8 @@ class AXPMenuSearchDefinitionProvider {
4756
4973
  }
4757
4974
  }
4758
4975
 
4976
+ const AXP_SEARCH_DEFINITION_LAZY_RESOLVER = new InjectionToken('AXP_SEARCH_DEFINITION_LAZY_RESOLVER');
4977
+
4759
4978
  class AXPSearchDefinitionProviderContext {
4760
4979
  constructor() {
4761
4980
  this.definitions = [];
@@ -4830,6 +5049,7 @@ const AXP_SEARCH_DEFINITION_PROVIDER = new InjectionToken('AXP_SEARCH_DEFINITION
4830
5049
  class AXPSearchDefinitionProviderService {
4831
5050
  constructor() {
4832
5051
  this.providers = inject(AXP_SEARCH_DEFINITION_PROVIDER, { optional: true });
5052
+ this.lazyResolver = inject(AXP_SEARCH_DEFINITION_LAZY_RESOLVER, { optional: true });
4833
5053
  this.cache = null;
4834
5054
  }
4835
5055
  async load() {
@@ -4860,12 +5080,29 @@ class AXPSearchDefinitionProviderService {
4860
5080
  }
4861
5081
  }
4862
5082
  findDefinition(definitionName) {
4863
- const definition = this.getList().find((definition) => definition.name === definitionName);
5083
+ const definition = this.getList().find((item) => item.name === definitionName);
4864
5084
  if (!definition) {
4865
5085
  throw new Error(`Definition with name ${definitionName} not found`);
4866
5086
  }
4867
5087
  return definition;
4868
5088
  }
5089
+ async findDefinitionAsync(definitionName) {
5090
+ await this.load();
5091
+ const existing = this.getList().find((item) => item.name === definitionName);
5092
+ if (existing) {
5093
+ return existing;
5094
+ }
5095
+ const lazy = this.lazyResolver;
5096
+ if (!lazy) {
5097
+ return null;
5098
+ }
5099
+ const resolved = await lazy.resolve(definitionName);
5100
+ if (!resolved) {
5101
+ return null;
5102
+ }
5103
+ this.cache = [...(this.cache ?? []), resolved];
5104
+ return resolved;
5105
+ }
4869
5106
  findDefinitionByGroup(definitionGroup) {
4870
5107
  const definition = this.getList().find((definition) => definition.group === definitionGroup);
4871
5108
  if (!definition) {
@@ -4883,188 +5120,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4883
5120
 
4884
5121
  //#region ---- Imports ----
4885
5122
  //#endregion
4886
- //#region ---- Template normalization ----
4887
- /**
4888
- * Normalizes entity display templates for {@link AXPExpressionEvaluatorService}.
4889
- * Converts `context.eval('path')`, single braces, and simple dot paths to `context.eval` segments.
4890
- */
4891
- function normalizeEntityDisplayTemplate(template) {
4892
- let normalized = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ context.eval("$1") }}');
4893
- if (!normalized.includes('{{')) {
4894
- normalized = normalized.replace(/\{/g, '{{').replace(/\}/g, '}}');
4895
- }
4896
- return normalized.replace(/\{\{\s*(?!context\.)([\w]+(?:\.[\w]+)*)\s*\}\}/g, '{{ context.eval("$1") }}');
4897
- }
4898
- /** True when formatted text still contains unresolved template markers. */
4899
- function isUnresolvedEntityDisplayTemplate(value) {
4900
- return /\{\{/.test(value) || /context\.eval\s*\(/.test(value) || value === '[object Object]';
4901
- }
5123
+ //#region ---- Searchable text resolution ----
4902
5124
  /**
4903
- * Strips literal `undefined` / `null` tokens produced when expression segments are missing.
5125
+ * Resolves a value to plain text for global-search matching (i18n keys, locale maps, scalars).
4904
5126
  */
4905
- function sanitizeResolvedEntityDisplayTitle(value) {
4906
- const trimmed = value.trim();
4907
- if (!trimmed) {
5127
+ function resolveSearchableText(value, locale, translationService) {
5128
+ if (value == null || value === false) {
4908
5129
  return '';
4909
5130
  }
4910
- const withoutLiterals = trimmed
4911
- .replace(/\bundefined\b/gi, ' ')
4912
- .replace(/\bnull\b/gi, ' ')
4913
- .replace(/\s+/g, ' ')
4914
- .trim();
4915
- return withoutLiterals
4916
- .replace(/^[\s\-–—·|/,()+]+|[\s\-–—·|/,()+]+$/g, '')
4917
- .trim();
4918
- }
4919
- /** True when a resolved title is non-empty and safe to show in search or entity chrome. */
4920
- function isMeaningfulEntityDisplayTitle(value) {
4921
- const sanitized = sanitizeResolvedEntityDisplayTitle(value);
4922
- return sanitized.length > 0 && !isUnresolvedEntityDisplayTemplate(sanitized);
4923
- }
4924
- function isRowBoundTemplate(template) {
4925
- const trimmed = template.trim();
4926
- if (!trimmed || trimmed.startsWith('@')) {
4927
- return false;
4928
- }
4929
- return trimmed.includes('{{') || trimmed.includes('{');
4930
- }
4931
- //#endregion
4932
- //#region ---- Entity metadata ----
4933
- const DEFAULT_DISPLAY_FIELD_CANDIDATES = [
4934
- 'title',
4935
- 'name',
4936
- 'code',
4937
- 'fullName',
4938
- 'displayName',
4939
- 'subject',
4940
- 'description',
4941
- ];
4942
- function entityHasProperty(entity, path) {
4943
- const root = path.split('.')[0];
4944
- return entity.properties?.some((property) => property.name === root) ?? false;
4945
- }
4946
- function resolveEntityDisplayPropertyPaths(entity) {
4947
- const paths = [];
4948
- const add = (path) => {
4949
- const trimmed = path?.trim();
4950
- if (!trimmed || trimmed.includes('{') || paths.includes(trimmed)) {
4951
- return;
4952
- }
4953
- if (entityHasProperty(entity, trimmed)) {
4954
- paths.push(trimmed);
4955
- }
4956
- };
4957
- const lookupFormat = entity.formats?.lookup?.trim();
4958
- if (lookupFormat && !lookupFormat.includes('{')) {
4959
- add(lookupFormat);
4960
- }
4961
- for (const candidate of DEFAULT_DISPLAY_FIELD_CANDIDATES) {
4962
- add(candidate);
4963
- }
4964
- const identifierPlugin = entity.plugins?.find((plugin) => plugin.name === 'identifier');
4965
- const identifierField = identifierPlugin?.options?.['field'];
4966
- if (typeof identifierField === 'string') {
4967
- add(identifierField);
4968
- }
4969
- if (paths.length === 0) {
4970
- const firstStringProperty = entity.properties?.find((property) => property.schema?.dataType === 'string' && property.name !== 'id');
4971
- add(firstStringProperty?.name);
4972
- }
4973
- return paths;
4974
- }
4975
- /**
4976
- * Builds entity-scoped fallback metadata for global search (registered on each entity search definition).
4977
- */
4978
- function buildEntitySearchTitleContext(entity, primaryTemplate) {
4979
- const fallbackTemplates = [];
4980
- const normalizedPrimary = primaryTemplate?.trim()
4981
- ? normalizeEntityDisplayTemplate(primaryTemplate.trim())
4982
- : undefined;
4983
- const addTemplate = (raw) => {
4984
- const trimmed = raw?.trim();
4985
- if (!trimmed || !isRowBoundTemplate(trimmed)) {
4986
- return;
4987
- }
4988
- const normalized = normalizeEntityDisplayTemplate(trimmed);
4989
- if (!normalized || normalized === normalizedPrimary || fallbackTemplates.includes(normalized)) {
4990
- return;
4991
- }
4992
- fallbackTemplates.push(normalized);
4993
- };
4994
- addTemplate(entity.interfaces?.master?.single?.title);
4995
- addTemplate(entity.formats?.lookup);
4996
- addTemplate(entity.formats?.individual);
4997
- return {
4998
- fallbackTemplates,
4999
- fallbackFields: resolveEntityDisplayPropertyPaths(entity),
5000
- };
5001
- }
5002
- /**
5003
- * Row title template chain aligned with entity layouts.
5004
- * Priority: `interfaces.master.single.title` → `formats.searchResult.title` → `formats.individual`.
5005
- */
5006
- function resolveEntityRowTitleTemplate(entity) {
5007
- if (!entity) {
5008
- return undefined;
5009
- }
5010
- const singleTitle = entity.interfaces?.master?.single?.title?.trim();
5011
- if (singleTitle && isRowBoundTemplate(singleTitle)) {
5012
- return normalizeEntityDisplayTemplate(singleTitle);
5013
- }
5014
- const searchTitle = entity.formats?.searchResult?.title?.trim();
5015
- if (searchTitle && isRowBoundTemplate(searchTitle)) {
5016
- return normalizeEntityDisplayTemplate(searchTitle);
5017
- }
5018
- const lookupFormat = entity.formats?.lookup?.trim();
5019
- if (lookupFormat && isRowBoundTemplate(lookupFormat)) {
5020
- return normalizeEntityDisplayTemplate(lookupFormat);
5021
- }
5022
- const individual = entity.formats?.individual?.trim();
5023
- if (individual && isRowBoundTemplate(individual)) {
5024
- return normalizeEntityDisplayTemplate(individual);
5025
- }
5026
- return undefined;
5027
- }
5028
- //#endregion
5029
- //#region ---- Runtime resolution ----
5030
- /**
5031
- * Resolves an entity row title using entity-driven fallback templates and property paths.
5032
- */
5033
- async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator) {
5034
- if (data == null) {
5035
- return '';
5036
- }
5037
- for (const template of format.titleFallbackTemplates ?? []) {
5038
- const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator);
5039
- const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
5040
- ? resolved
5041
- : resolveSearchableText(resolved, locale, translationService));
5042
- if (isMeaningfulEntityDisplayTitle(text)) {
5043
- return text;
5044
- }
5045
- }
5046
- for (const path of format.titleFallbackFields ?? []) {
5047
- const text = resolveSearchableText(get(data, path), locale, translationService).trim();
5048
- if (text) {
5049
- return text;
5050
- }
5051
- }
5052
- return '';
5053
- }
5054
- //#endregion
5055
-
5056
- //#region ---- Imports ----
5057
- //#endregion
5058
- //#region ---- Searchable text resolution ----
5059
- /**
5060
- * Resolves a value to plain text for global-search matching (i18n keys, locale maps, scalars).
5061
- */
5062
- function resolveSearchableText(value, locale, translationService) {
5063
- if (value == null || value === false) {
5064
- return '';
5065
- }
5066
- if (typeof value === 'number' || typeof value === 'boolean') {
5067
- return String(value);
5131
+ if (typeof value === 'number' || typeof value === 'boolean') {
5132
+ return String(value);
5068
5133
  }
5069
5134
  if (typeof value === 'string') {
5070
5135
  const trimmed = value.trim();
@@ -5076,13 +5141,8 @@ function resolveSearchableText(value, locale, translationService) {
5076
5141
  }
5077
5142
  return trimmed;
5078
5143
  }
5079
- if (isLocaleStringMap(value)) {
5080
- return Object.values(value)
5081
- .filter((entry) => (entry ?? '').trim() !== '')
5082
- .join(' ');
5083
- }
5084
- if (translationService.isValidMultiLanguageObject(value)) {
5085
- return resolveMultiLanguageString(value, locale);
5144
+ if (isLocaleStringMap(value) || translationService.isValidMultiLanguageObject(value)) {
5145
+ return resolveMultiLanguageString(value, locale).trim();
5086
5146
  }
5087
5147
  return '';
5088
5148
  }
@@ -5094,6 +5154,11 @@ function searchInSearchableValue(value, query, locale, translationService) {
5094
5154
  if (!q) {
5095
5155
  return true;
5096
5156
  }
5157
+ if (isLocaleStringMap(value)) {
5158
+ return Object.values(value)
5159
+ .filter((entry) => typeof entry === 'string' && entry.trim() !== '')
5160
+ .some((entry) => entry.toLocaleLowerCase(locale).includes(q));
5161
+ }
5097
5162
  const text = resolveSearchableText(value, locale, translationService).toLocaleLowerCase(locale);
5098
5163
  return text.includes(q);
5099
5164
  }
@@ -5126,26 +5191,31 @@ function searchInRecordValues(value, query, locale, translationService, depth =
5126
5191
  function countMustacheExpressions(template) {
5127
5192
  return (template.match(/\{\{/g) ?? []).length;
5128
5193
  }
5129
- /** Standard row scope for entity title/search templates. */
5130
- function createEntityRowExpressionScope(data) {
5194
+ /** Standard display-tier row scope for entity title/search templates. */
5195
+ function createEntityRowExpressionScope(data, contextEvalFactory) {
5131
5196
  return {
5132
- context: {
5133
- eval: (path) => get(data, path),
5134
- },
5197
+ context: contextEvalFactory.createScope((data ?? {})),
5135
5198
  };
5136
5199
  }
5200
+ /**
5201
+ * Coerces an evaluated display segment to plain text.
5202
+ * Piped eval returns strings; unpiped MLS still resolves via compat fallback.
5203
+ */
5204
+ function formatDisplaySegment(segment, locale, translationService) {
5205
+ if (segment === false || segment == null) {
5206
+ return '';
5207
+ }
5208
+ if (isString(segment)) {
5209
+ return segment.trim();
5210
+ }
5211
+ return resolveSearchableText(segment, locale, translationService);
5212
+ }
5137
5213
  /**
5138
5214
  * Evaluates a normalized entity display template via {@link AXPExpressionEvaluatorService}.
5139
5215
  */
5140
- async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService) {
5216
+ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory) {
5141
5217
  const normalized = normalizeEntityDisplayTemplate(template);
5142
- const scope = createEntityRowExpressionScope(data);
5143
- const formatSegment = (segment) => {
5144
- if (segment === false || segment == null) {
5145
- return '';
5146
- }
5147
- return resolveSearchableText(segment, locale, translationService);
5148
- };
5218
+ const scope = createEntityRowExpressionScope(data, contextEvalFactory);
5149
5219
  if (!normalized.includes('{{')) {
5150
5220
  return normalized;
5151
5221
  }
@@ -5159,7 +5229,7 @@ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator
5159
5229
  for (const match of normalized.matchAll(regex)) {
5160
5230
  result += normalized.slice(lastIndex, match.index);
5161
5231
  const segment = await expressionEvaluator.evaluate(`{{ ${match[1].trim()} }}`, scope);
5162
- result += formatSegment(segment);
5232
+ result += formatDisplaySegment(segment, locale, translationService);
5163
5233
  lastIndex = (match.index ?? 0) + match[0].length;
5164
5234
  }
5165
5235
  result += normalized.slice(lastIndex);
@@ -5206,7 +5276,7 @@ function resolveQueryMatchingDisplayText(value, query, locale, translationServic
5206
5276
  /**
5207
5277
  * Resolves entity search display templates; coerces multi-language field values to readable text.
5208
5278
  */
5209
- async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator) {
5279
+ async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
5210
5280
  if (!template) {
5211
5281
  return '';
5212
5282
  }
@@ -5217,7 +5287,7 @@ async function resolveSearchDisplayTemplate(template, data, locale, translationS
5217
5287
  const formatted = formatService.format(template, 'string', data);
5218
5288
  return sanitizeResolvedEntityDisplayTitle(typeof formatted === 'string' ? formatted : String(formatted ?? ''));
5219
5289
  }
5220
- const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService);
5290
+ const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory);
5221
5291
  return sanitizeResolvedEntityDisplayTitle(resolveSearchableText(evaluated, locale, translationService));
5222
5292
  }
5223
5293
  //#endregion
@@ -5233,6 +5303,56 @@ class AXPSearchCommandProvider {
5233
5303
  }
5234
5304
  }
5235
5305
 
5306
+ //#region ---- Imports ----
5307
+ //#endregion
5308
+ //#region ---- Runtime resolution ----
5309
+ /**
5310
+ * Modify/edit popup chrome: `{action} {individual} ({update.title ?? single.title})` when a record template exists.
5311
+ */
5312
+ async function resolveEntityModifyPopupTitle(entity, recordData, translationService, formatService, expressionEvaluator, contextEvalFactory, actionTitleKey = '@general:actions.modify.title') {
5313
+ const actionPrefix = translationService.translateSync(actionTitleKey);
5314
+ const entityLabel = translationService.translateSync(entity.formats.individual);
5315
+ const baseTitle = `${actionPrefix} ${entityLabel}`.trim();
5316
+ const recordTemplate = resolveEntityModifyRecordTitleTemplate(entity);
5317
+ if (!recordTemplate) {
5318
+ return baseTitle;
5319
+ }
5320
+ const locale = translationService.getActiveLang();
5321
+ const resolved = await resolveSearchDisplayTemplate(recordTemplate, recordData, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
5322
+ const recordTitle = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
5323
+ ? resolved
5324
+ : resolveSearchableText(resolved, locale, translationService));
5325
+ if (!isMeaningfulEntityDisplayTitle(recordTitle)) {
5326
+ return baseTitle;
5327
+ }
5328
+ return `${baseTitle} (${recordTitle})`;
5329
+ }
5330
+ /**
5331
+ * Resolves an entity row title using entity-driven fallback templates and property paths.
5332
+ */
5333
+ async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
5334
+ if (data == null) {
5335
+ return '';
5336
+ }
5337
+ for (const template of format.titleFallbackTemplates ?? []) {
5338
+ const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
5339
+ const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
5340
+ ? resolved
5341
+ : resolveSearchableText(resolved, locale, translationService));
5342
+ if (isMeaningfulEntityDisplayTitle(text)) {
5343
+ return text;
5344
+ }
5345
+ }
5346
+ for (const path of format.titleFallbackFields ?? []) {
5347
+ const text = resolveSearchableText(get(data, path), locale, translationService).trim();
5348
+ if (text) {
5349
+ return text;
5350
+ }
5351
+ }
5352
+ return '';
5353
+ }
5354
+ //#endregion
5355
+
5236
5356
  // Injection token for setting providers
5237
5357
  const AXP_SEARCH_PROVIDER = new InjectionToken('AXP_SEARCH_PROVIDER');
5238
5358
  class AXPSearchService {
@@ -5242,6 +5362,7 @@ class AXPSearchService {
5242
5362
  this.formatService = inject(AXFormatService);
5243
5363
  this.translationService = inject(AXTranslationService);
5244
5364
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
5365
+ this.contextEvalFactory = inject(AXPContextEvalFactory);
5245
5366
  }
5246
5367
  async search(text) {
5247
5368
  //TODO better handle this
@@ -5271,20 +5392,23 @@ class AXPSearchService {
5271
5392
  for (const resultArray of results) {
5272
5393
  for (const result of resultArray) {
5273
5394
  try {
5274
- const definition = this.definitionService.findDefinition(result.group);
5395
+ const definition = await this.definitionService.findDefinitionAsync(result.group);
5396
+ if (!definition) {
5397
+ continue;
5398
+ }
5275
5399
  const resolvedTitle = result.title ??
5276
5400
  (definition.format.title
5277
- ? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
5401
+ ? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
5278
5402
  : '');
5279
5403
  const resolvedDescription = result.description ??
5280
5404
  (definition.format.description
5281
- ? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
5405
+ ? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
5282
5406
  : undefined);
5283
5407
  let titleText = sanitizeResolvedEntityDisplayTitle(typeof resolvedTitle === 'string'
5284
5408
  ? resolvedTitle
5285
5409
  : resolveSearchableText(resolvedTitle, locale, this.translationService));
5286
5410
  if (!isMeaningfulEntityDisplayTitle(titleText)) {
5287
- titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator);
5411
+ titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory);
5288
5412
  }
5289
5413
  if (!isMeaningfulEntityDisplayTitle(titleText)) {
5290
5414
  continue;
@@ -5349,12 +5473,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
5349
5473
  //#region ---- Constants ----
5350
5474
  const INVALID_DISPLAY_TEXT = '[object Object]';
5351
5475
  const DEFAULT_ROW_TITLE_FALLBACK_PATHS = ['title', 'name', 'code'];
5352
- /** Row path used when context carries a nested entity layout title. */
5353
- const ENTITY_LAYOUT_SINGLE_TITLE_PATH = 'entity.layout.single.title';
5354
- /** Widget option for a pre-evaluated or template entity row title (entity list column mappers). */
5355
- const ENTITY_ROW_POPUP_TITLE_OPTION = 'popupTitle';
5356
- /** Widget option alias for entity row title templates in view/edit forms. */
5357
- const ENTITY_ROW_TITLE_TEMPLATE_OPTION = 'titleTemplate';
5358
5476
  //#endregion
5359
5477
  //#region ---- Value normalization ----
5360
5478
  /** Resolves a row field or format result to a plain display string for the active locale. */
@@ -5658,126 +5776,1106 @@ class AXPTokenDefinitionService {
5658
5776
  flattenedProviders.push(provider);
5659
5777
  }
5660
5778
  }
5661
- const match = flattenedProviders.find(def => def?.name === name);
5662
- if (match) {
5663
- this.definitionsByName.set(name, match);
5664
- return match;
5779
+ const match = flattenedProviders.find(def => def?.name === name);
5780
+ if (match) {
5781
+ this.definitionsByName.set(name, match);
5782
+ return match;
5783
+ }
5784
+ return undefined;
5785
+ }
5786
+ /**
5787
+ * Gets the resolved value of a token by its name.
5788
+ * @param name The token name.
5789
+ * @returns The resolved token value, or undefined if the token is not found.
5790
+ */
5791
+ async getValue(name) {
5792
+ const definition = await this.getDefinition(name);
5793
+ if (definition) {
5794
+ return await definition.execute();
5795
+ }
5796
+ return undefined;
5797
+ }
5798
+ //#endregion
5799
+ //#region ---- Cache Management ----
5800
+ /** Clears the definitions by name cache. */
5801
+ clearDefinitionsCache() {
5802
+ this.definitionsByName.clear();
5803
+ }
5804
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5805
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, providedIn: 'root' }); }
5806
+ }
5807
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, decorators: [{
5808
+ type: Injectable,
5809
+ args: [{
5810
+ providedIn: 'root',
5811
+ }]
5812
+ }] });
5813
+
5814
+ class AXPTokenEvaluatorScopeProvider {
5815
+ constructor() {
5816
+ this.tokenService = inject(AXPTokenDefinitionService);
5817
+ }
5818
+ async provide(context) {
5819
+ context.addScope('tokens', {
5820
+ get: async (name) => {
5821
+ return await this.tokenService.getValue(name);
5822
+ }
5823
+ });
5824
+ }
5825
+ }
5826
+
5827
+ //#region ---- Tab Key ----
5828
+ /** Query params ignored when building a tab dedupe key. */
5829
+ const IGNORED_QUERY_KEYS = new Set(['_ts']);
5830
+ /**
5831
+ * Builds a stable tab key from a router URL.
5832
+ * Path segments define identity; query params are excluded so the same entity record
5833
+ * or list reuses one tab even when filters or refresh tokens change.
5834
+ */
5835
+ function axpBuildTabKey(url) {
5836
+ const normalized = normalizeRouterUrl(url);
5837
+ const pathOnly = normalized.split('?')[0].split('#')[0];
5838
+ return pathOnly;
5839
+ }
5840
+ /** Normalizes a router URL for storage and navigation. */
5841
+ function normalizeRouterUrl(url) {
5842
+ const trimmed = url.trim();
5843
+ if (!trimmed.length) {
5844
+ return '/';
5845
+ }
5846
+ const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
5847
+ const [pathAndFragment, queryString = ''] = withLeadingSlash.split('?');
5848
+ const [pathPart, fragment = ''] = pathAndFragment.split('#');
5849
+ const normalizedPath = pathPart.replace(/\/+$/, '') || '/';
5850
+ const filteredQuery = filterQueryString(queryString);
5851
+ let result = normalizedPath;
5852
+ if (filteredQuery) {
5853
+ result += `?${filteredQuery}`;
5854
+ }
5855
+ if (fragment) {
5856
+ result += `#${fragment}`;
5857
+ }
5858
+ return result;
5859
+ }
5860
+ function filterQueryString(queryString) {
5861
+ if (!queryString) {
5862
+ return '';
5863
+ }
5864
+ const params = new URLSearchParams(queryString);
5865
+ const kept = [];
5866
+ params.forEach((value, key) => {
5867
+ if (!IGNORED_QUERY_KEYS.has(key)) {
5868
+ kept.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
5869
+ }
5870
+ });
5871
+ kept.sort();
5872
+ return kept.join('&');
5873
+ }
5874
+ /** Human-readable fallback title from the URL path. */
5875
+ function axpFallbackTabTitle(url) {
5876
+ const pathOnly = url.split('?')[0].split('#')[0];
5877
+ const segments = pathOnly.split('/').filter(Boolean);
5878
+ const last = defaultTo(segments[segments.length - 1], 'Page');
5879
+ return decodeURIComponent(last);
5880
+ }
5881
+ //#endregion
5882
+
5883
+ //#region ---- Types ----
5884
+ const AXP_HOME_TAB_KEY = 'axp:home';
5885
+ /** Route `data` key for tab-navigation behavior (`data.tab`). */
5886
+ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
5887
+ //#endregion
5888
+
5889
+ //#region ---- Tab Preview Service ----
5890
+ /**
5891
+ * In-memory structured previews for tab hover cards and dirty indicators.
5892
+ * Populated by active/cached pages via {@link AXPPageLayoutBaseComponent}.
5893
+ */
5894
+ class AXPTabPreviewService {
5895
+ constructor() {
5896
+ //#region ---- State ----
5897
+ this.previewMap = signal({}, ...(ngDevMode ? [{ debugName: "previewMap" }] : /* istanbul ignore next */ []));
5898
+ this.previews = this.previewMap.asReadonly();
5899
+ }
5900
+ //#endregion
5901
+ //#region ---- Public API ----
5902
+ setPreview(key, preview) {
5903
+ this.previewMap.update((current) => ({
5904
+ ...current,
5905
+ [key]: preview,
5906
+ }));
5907
+ }
5908
+ patchPreview(key, patch) {
5909
+ this.previewMap.update((current) => {
5910
+ const existing = current[key];
5911
+ if (!existing) {
5912
+ if (!patch.url) {
5913
+ return current;
5914
+ }
5915
+ return {
5916
+ ...current,
5917
+ [key]: {
5918
+ title: patch.title ?? '',
5919
+ url: patch.url,
5920
+ icon: patch.icon,
5921
+ subtitle: patch.subtitle,
5922
+ isDirty: patch.isDirty ?? false,
5923
+ breadcrumbTrail: patch.breadcrumbTrail ?? [],
5924
+ },
5925
+ };
5926
+ }
5927
+ return {
5928
+ ...current,
5929
+ [key]: {
5930
+ ...existing,
5931
+ ...patch,
5932
+ },
5933
+ };
5934
+ });
5935
+ }
5936
+ getPreview(key) {
5937
+ return this.previewMap()[key];
5938
+ }
5939
+ isTabDirty(key) {
5940
+ return !!this.previewMap()[key]?.isDirty;
5941
+ }
5942
+ clearPreview(key) {
5943
+ this.previewMap.update((current) => {
5944
+ if (!(key in current)) {
5945
+ return current;
5946
+ }
5947
+ const next = { ...current };
5948
+ delete next[key];
5949
+ return next;
5950
+ });
5951
+ }
5952
+ clearPreviews(keys) {
5953
+ const keySet = new Set(keys);
5954
+ if (!keySet.size) {
5955
+ return;
5956
+ }
5957
+ this.previewMap.update((current) => {
5958
+ const next = { ...current };
5959
+ let changed = false;
5960
+ for (const key of keySet) {
5961
+ if (key in next) {
5962
+ delete next[key];
5963
+ changed = true;
5964
+ }
5965
+ }
5966
+ return changed ? next : current;
5967
+ });
5968
+ }
5969
+ clearAll() {
5970
+ this.previewMap.set({});
5971
+ }
5972
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5973
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, providedIn: 'root' }); }
5974
+ }
5975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, decorators: [{
5976
+ type: Injectable,
5977
+ args: [{
5978
+ providedIn: 'root',
5979
+ }]
5980
+ }] });
5981
+
5982
+ //#region ---- Tab Route Policy ----
5983
+ const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
5984
+ const EXCLUDED_PATH_PATTERNS = [
5985
+ /^\/assessment\/fill\//i,
5986
+ /^\/forms\//i,
5987
+ ];
5988
+ /**
5989
+ * Returns true when the URL represents an in-app page that may appear in the tab bar.
5990
+ */
5991
+ function axpIsTabEligibleUrl(url) {
5992
+ const pathOnly = url.split('?')[0].split('#')[0].trim();
5993
+ if (!pathOnly || pathOnly === '/') {
5994
+ return false;
5995
+ }
5996
+ const normalized = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
5997
+ if (EXCLUDED_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
5998
+ return false;
5999
+ }
6000
+ if (EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(normalized))) {
6001
+ return false;
6002
+ }
6003
+ return true;
6004
+ }
6005
+ //#endregion
6006
+
6007
+ //#region ---- Route Data ----
6008
+ /** True when the route declares itself as a scoped tab root (`data.tab = true`). */
6009
+ function axpHasTabScope(data) {
6010
+ return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
6011
+ }
6012
+ //#endregion
6013
+ //#region ---- Snapshot Resolution ----
6014
+ /** Resolved URL path for a snapshot (no query or fragment). */
6015
+ function axpPathFromSnapshot(snapshot) {
6016
+ const segments = snapshot.pathFromRoot
6017
+ .flatMap((entry) => entry.url.map((segment) => segment.path))
6018
+ .filter(Boolean);
6019
+ return segments.length ? `/${segments.join('/')}` : '/';
6020
+ }
6021
+ /** Returns the leaf snapshot under the router state root. */
6022
+ function axpGetLeafRouteSnapshot(root) {
6023
+ let route = root;
6024
+ while (route.firstChild) {
6025
+ route = route.firstChild;
6026
+ }
6027
+ return route;
6028
+ }
6029
+ /**
6030
+ * Resolves the tab dedupe key from an activated route tree.
6031
+ * Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
6032
+ */
6033
+ function axpResolveTabKeyFromSnapshot(leaf) {
6034
+ const chain = leaf.pathFromRoot;
6035
+ for (let index = chain.length - 1; index >= 0; index--) {
6036
+ const snapshot = chain[index];
6037
+ if (axpHasTabScope(snapshot.data)) {
6038
+ return axpPathFromSnapshot(snapshot);
6039
+ }
6040
+ }
6041
+ return axpPathFromSnapshot(leaf);
6042
+ }
6043
+ //#endregion
6044
+ //#region ---- Router / URL Resolution ----
6045
+ /**
6046
+ * Resolves a tab key for the given URL (or the current router URL).
6047
+ * Prefers the live route snapshot when it matches the target URL; otherwise matches route config.
6048
+ */
6049
+ function axpResolveTabKey(router, url) {
6050
+ const normalized = normalizeRouterUrl(url ?? router.url);
6051
+ const pathOnly = normalized.split('?')[0].split('#')[0];
6052
+ const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
6053
+ if (!url || pathOnly === currentPath) {
6054
+ const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
6055
+ return axpResolveTabKeyFromSnapshot(leaf);
6056
+ }
6057
+ const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
6058
+ if (fromConfig) {
6059
+ return fromConfig;
6060
+ }
6061
+ return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
6062
+ }
6063
+ /**
6064
+ * Matches a URL path against registered routes and resolves the tab key from `data.tab`.
6065
+ */
6066
+ function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
6067
+ const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
6068
+ const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
6069
+ const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
6070
+ if (!chain?.length) {
6071
+ return null;
6072
+ }
6073
+ for (let index = chain.length - 1; index >= 0; index--) {
6074
+ const level = chain[index];
6075
+ if (axpHasTabScope(level.mergedData)) {
6076
+ return level.path;
6077
+ }
6078
+ }
6079
+ return chain[chain.length - 1]?.path ?? null;
6080
+ }
6081
+ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
6082
+ let bestMatch = null;
6083
+ for (const route of routes) {
6084
+ if (route.redirectTo) {
6085
+ continue;
6086
+ }
6087
+ const segmentMatch = matchRoutePath(route.path ?? '', segments, startIndex);
6088
+ if (!segmentMatch) {
6089
+ continue;
6090
+ }
6091
+ const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
6092
+ const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
6093
+ const mergedData = { ...inheritedData, ...(route.data ?? {}) };
6094
+ const level = { mergedData, path };
6095
+ const nextChain = [...chain, level];
6096
+ const nextIndex = startIndex + segmentMatch.consumed;
6097
+ if (route.children?.length) {
6098
+ const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
6099
+ if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
6100
+ bestMatch = childMatch;
6101
+ }
6102
+ }
6103
+ if (nextIndex === segments.length) {
6104
+ if (!bestMatch || nextChain.length > bestMatch.length) {
6105
+ bestMatch = nextChain;
6106
+ }
6107
+ }
6108
+ }
6109
+ return bestMatch;
6110
+ }
6111
+ function matchRoutePath(routePath, segments, startIndex) {
6112
+ if (routePath === '**') {
6113
+ return { consumed: Math.max(0, segments.length - startIndex) };
6114
+ }
6115
+ const parts = routePath.split('/').filter(Boolean);
6116
+ if (!parts.length) {
6117
+ return { consumed: 0 };
6118
+ }
6119
+ let consumed = 0;
6120
+ for (const part of parts) {
6121
+ const segment = segments[startIndex + consumed];
6122
+ if (segment === undefined) {
6123
+ return null;
6124
+ }
6125
+ if (part.startsWith(':') || part === segment) {
6126
+ consumed++;
6127
+ continue;
6128
+ }
6129
+ return null;
6130
+ }
6131
+ return { consumed };
6132
+ }
6133
+ //#endregion
6134
+
6135
+ //#region ---- Constants ----
6136
+ const SESSION_STORAGE_PREFIX = 'AXP_TAB_NAV';
6137
+ //#endregion
6138
+ class AXPTabNavigationService {
6139
+ //#endregion
6140
+ //#region ---- Lifecycle ----
6141
+ constructor() {
6142
+ //#region ---- Services & Dependencies ----
6143
+ this.router = inject(Router);
6144
+ this.settingsService = inject(AXPSettingsService);
6145
+ this.deviceService = inject(AXPDeviceService);
6146
+ this.menuService = inject(AXPMenuService);
6147
+ this.homePageService = inject(AXPHomePageService);
6148
+ this.sessionService = inject(AXPSessionService);
6149
+ this.routeCacheRegistry = inject(AXPTabRouteCacheRegistry);
6150
+ this.tabPreviewService = inject(AXPTabPreviewService);
6151
+ this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
6152
+ this.destroyRef = inject(DestroyRef);
6153
+ //#endregion
6154
+ //#region ---- State ----
6155
+ this.navigationMode = signal('simple', ...(ngDevMode ? [{ debugName: "navigationMode" }] : /* istanbul ignore next */ []));
6156
+ this.maxOpenTabs = signal(5, ...(ngDevMode ? [{ debugName: "maxOpenTabs" }] : /* istanbul ignore next */ []));
6157
+ this.tabs = signal([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
6158
+ this.activeTabKey = signal(null, ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
6159
+ this.suppressNavigationSync = false;
6160
+ //#endregion
6161
+ //#region ---- Computed ----
6162
+ /** Effective mode: mobile always uses simple navigation. */
6163
+ this.effectiveMode = computed(() => {
6164
+ if (this.deviceService.isSmall()) {
6165
+ return 'simple';
6166
+ }
6167
+ return this.navigationMode();
6168
+ }, ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
6169
+ this.isTabBarVisible = computed(() => this.effectiveMode() === 'tabular' && this.tabs().length > 0, ...(ngDevMode ? [{ debugName: "isTabBarVisible" }] : /* istanbul ignore next */ []));
6170
+ this.tabList = this.tabs.asReadonly();
6171
+ this.activeKey = this.activeTabKey.asReadonly();
6172
+ void this.loadSettings();
6173
+ this.bindRouterEvents();
6174
+ this.bindSettingsChanges();
6175
+ this.bindSessionChanges();
6176
+ this.bindRouteCacheSync();
6177
+ }
6178
+ //#endregion
6179
+ //#region ---- Public API ----
6180
+ /** Navigates to a path, opening or activating a tab when tabular mode is active. */
6181
+ async navigate(path, extras) {
6182
+ const normalizedPath = normalizeRouterUrl(path);
6183
+ if (this.effectiveMode() === 'simple' || !axpIsTabEligibleUrl(normalizedPath)) {
6184
+ return axpNavigateAppPath(this.router, normalizedPath, extras);
6185
+ }
6186
+ await this.openOrActivateTab(normalizedPath);
6187
+ return true;
6188
+ }
6189
+ /** Opens a tab or activates an existing tab with the same key. */
6190
+ async openOrActivateTab(url, title) {
6191
+ if (this.effectiveMode() === 'simple') {
6192
+ await axpNavigateAppPath(this.router, url);
6193
+ return;
6194
+ }
6195
+ const normalizedUrl = normalizeRouterUrl(url);
6196
+ if (!axpIsTabEligibleUrl(normalizedUrl)) {
6197
+ await axpNavigateAppPath(this.router, normalizedUrl);
6198
+ return;
6199
+ }
6200
+ await this.ensureHomeTab();
6201
+ if (this.isHomePageUrl(normalizedUrl)) {
6202
+ await this.activateTabKey(AXP_HOME_TAB_KEY, normalizedUrl);
6203
+ return;
6204
+ }
6205
+ const resolvedKey = axpResolveTabKey(this.router, normalizedUrl);
6206
+ const existing = this.findTabForUrl(normalizedUrl);
6207
+ if (existing) {
6208
+ if (existing.key !== resolvedKey) {
6209
+ this.migrateTabKey(existing.key, resolvedKey);
6210
+ }
6211
+ this.updateTabUrl(resolvedKey, normalizedUrl);
6212
+ await this.activateTabKey(resolvedKey, normalizedUrl);
6213
+ return;
6214
+ }
6215
+ // Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
6216
+ this.suppressNavigationSync = true;
6217
+ try {
6218
+ await axpNavigateAppPath(this.router, normalizedUrl);
6219
+ const key = axpResolveTabKey(this.router);
6220
+ const postNavExisting = this.findTabForUrl(normalizedUrl);
6221
+ if (postNavExisting) {
6222
+ if (postNavExisting.key !== key) {
6223
+ this.migrateTabKey(postNavExisting.key, key);
6224
+ }
6225
+ this.updateTabUrl(key, normalizedUrl);
6226
+ this.touchTab(key);
6227
+ this.activeTabKey.set(key);
6228
+ this.persistSession();
6229
+ return;
6230
+ }
6231
+ this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
6232
+ this.activeTabKey.set(key);
6233
+ this.persistSession();
6234
+ }
6235
+ finally {
6236
+ this.suppressNavigationSync = false;
6237
+ }
6238
+ }
6239
+ /**
6240
+ * Activates a tab and navigates to its URL. The previously active page is detached and cached by
6241
+ * the reuse strategy, so its state is preserved; this tab's cached instance is reattached when
6242
+ * available instead of being re-rendered.
6243
+ *
6244
+ * @param suspendDeactivateGuard When true (default for tab switches), unsaved-changes prompts are
6245
+ * skipped so switching tabs keeps a dirty page alive. Closing a tab passes false to keep the prompt.
6246
+ */
6247
+ async activateTabKey(key, urlOverride, suspendDeactivateGuard = true) {
6248
+ const tab = this.tabs().find((item) => item.key === key);
6249
+ if (!tab) {
6250
+ return;
6251
+ }
6252
+ const targetUrl = urlOverride ?? tab.url;
6253
+ this.touchTab(key);
6254
+ this.activeTabKey.set(key);
6255
+ this.persistSession();
6256
+ const navigate = async () => {
6257
+ this.suppressNavigationSync = true;
6258
+ try {
6259
+ await this.router.navigateByUrl(targetUrl);
6260
+ }
6261
+ finally {
6262
+ this.suppressNavigationSync = false;
6263
+ }
6264
+ };
6265
+ if (suspendDeactivateGuard) {
6266
+ await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
6267
+ return;
6268
+ }
6269
+ await navigate();
6270
+ }
6271
+ /** Closes a tab and navigates to a neighbor when the active tab is closed. */
6272
+ async closeTab(key) {
6273
+ await this.closeTabs([key]);
6274
+ }
6275
+ /** Closes every closable tab except the given key (pinned tabs are kept). */
6276
+ async closeOtherTabs(keepKey) {
6277
+ const keys = this.tabs()
6278
+ .filter((tab) => tab.closable && !tab.pinned && tab.key !== keepKey)
6279
+ .map((tab) => tab.key);
6280
+ await this.closeTabs(keys);
6281
+ }
6282
+ /** Closes all closable tabs (pinned tabs are kept). */
6283
+ async closeAllTabs() {
6284
+ const keys = this.tabs()
6285
+ .filter((tab) => tab.closable && !tab.pinned)
6286
+ .map((tab) => tab.key);
6287
+ await this.closeTabs(keys);
6288
+ }
6289
+ /** Toggles whether a tab stays pinned in the strip (home tab is always pinned). */
6290
+ toggleTabPinned(key) {
6291
+ if (key === AXP_HOME_TAB_KEY) {
6292
+ return;
6293
+ }
6294
+ this.tabs.update((items) => {
6295
+ const updated = items.map((tab) => {
6296
+ if (tab.key !== key) {
6297
+ return tab;
6298
+ }
6299
+ const pinned = !tab.pinned;
6300
+ return {
6301
+ ...tab,
6302
+ pinned,
6303
+ closable: !pinned,
6304
+ };
6305
+ });
6306
+ return this.normalizeTabOrder(updated);
6307
+ });
6308
+ this.persistSession();
6309
+ }
6310
+ /**
6311
+ * Reorders tabs after drag-and-drop. Home stays first; pinned and unpinned tabs cannot cross groups.
6312
+ */
6313
+ reorderTabs(previousIndex, currentIndex) {
6314
+ if (previousIndex === currentIndex) {
6315
+ return;
6316
+ }
6317
+ const items = this.tabs();
6318
+ if (previousIndex < 0 || currentIndex < 0 || previousIndex >= items.length || currentIndex >= items.length) {
6319
+ return;
6320
+ }
6321
+ const moving = items[previousIndex];
6322
+ if (moving.key === AXP_HOME_TAB_KEY) {
6323
+ return;
6324
+ }
6325
+ const lastPinnedIndex = this.getLastPinnedTabIndex(items);
6326
+ let targetIndex = currentIndex;
6327
+ if (moving.pinned) {
6328
+ if (targetIndex === 0) {
6329
+ return;
6330
+ }
6331
+ targetIndex = Math.max(1, Math.min(targetIndex, lastPinnedIndex));
6332
+ }
6333
+ else {
6334
+ const firstUnpinnedIndex = lastPinnedIndex + 1;
6335
+ if (firstUnpinnedIndex >= items.length) {
6336
+ return;
6337
+ }
6338
+ targetIndex = Math.max(firstUnpinnedIndex, Math.min(targetIndex, items.length - 1));
6339
+ }
6340
+ if (previousIndex === targetIndex) {
6341
+ return;
6342
+ }
6343
+ const next = [...items];
6344
+ const [removed] = next.splice(previousIndex, 1);
6345
+ next.splice(targetIndex, 0, removed);
6346
+ this.tabs.set(next);
6347
+ this.persistSession();
6348
+ }
6349
+ /** Updates a tab title from the current page chrome (e.g. breadcrumb trail). */
6350
+ syncTabTitle(key, title) {
6351
+ if (this.effectiveMode() === 'simple') {
6352
+ return;
6353
+ }
6354
+ if (!this.tabs().some((tab) => tab.key === key)) {
6355
+ return;
6356
+ }
6357
+ const normalized = title.trim();
6358
+ if (!normalized || this.isGenericHomeTitle(normalized)) {
6359
+ return;
6360
+ }
6361
+ this.updateTabTitle(key, normalized);
6362
+ }
6363
+ /** Updates a tab icon from the current page chrome or menu metadata. */
6364
+ syncTabIcon(key, icon) {
6365
+ if (this.effectiveMode() === 'simple') {
6366
+ return;
6367
+ }
6368
+ if (!this.tabs().some((tab) => tab.key === key)) {
6369
+ return;
6370
+ }
6371
+ const normalized = icon.trim();
6372
+ if (!normalized) {
6373
+ return;
6374
+ }
6375
+ this.updateTabIcon(key, normalized);
6376
+ }
6377
+ //#endregion
6378
+ //#region ---- Bulk Tab Close ----
6379
+ /** Closes multiple tabs in one update; navigates once when the active tab is closed. */
6380
+ async closeTabs(keys) {
6381
+ const uniqueKeys = [...new Set(keys)];
6382
+ if (!uniqueKeys.length) {
6383
+ return;
6384
+ }
6385
+ const closableKeySet = new Set(uniqueKeys.filter((key) => {
6386
+ const tab = this.tabs().find((item) => item.key === key);
6387
+ return !!tab?.closable && !tab.pinned;
6388
+ }));
6389
+ if (!closableKeySet.size) {
6390
+ return;
6391
+ }
6392
+ const activeKey = this.activeTabKey();
6393
+ const backgroundKeys = [...closableKeySet].filter((key) => key !== activeKey);
6394
+ for (const key of backgroundKeys) {
6395
+ if (!(await this.confirmBackgroundTabClose(key))) {
6396
+ closableKeySet.delete(key);
6397
+ }
6398
+ }
6399
+ if (!closableKeySet.size) {
6400
+ return;
6401
+ }
6402
+ const currentTabs = this.tabs();
6403
+ const wasActiveClosed = !!activeKey && closableKeySet.has(activeKey);
6404
+ const closedActiveIndex = wasActiveClosed ? currentTabs.findIndex((item) => item.key === activeKey) : -1;
6405
+ const nextTabs = currentTabs.filter((item) => !closableKeySet.has(item.key));
6406
+ if (wasActiveClosed) {
6407
+ const navigated = await this.navigateAwayBeforeClosingActiveTab(closedActiveIndex, nextTabs);
6408
+ if (!navigated) {
6409
+ return;
6410
+ }
6411
+ }
6412
+ this.tabs.set(nextTabs);
6413
+ this.tabPreviewService.clearPreviews(closableKeySet);
6414
+ this.persistSession();
6415
+ }
6416
+ //#endregion
6417
+ //#region ---- Tab Close Helpers ----
6418
+ /**
6419
+ * Prompts before closing a cached background tab. Uses the page's {@link AXPTabCloseConfirm}
6420
+ * hook when available, otherwise falls back to the tab preview dirty flag.
6421
+ */
6422
+ async confirmBackgroundTabClose(key) {
6423
+ const instance = this.routeCacheRegistry.getComponentInstance(key);
6424
+ if (instance && typeof instance.onBeforeTabClose === 'function') {
6425
+ return instance.onBeforeTabClose();
6426
+ }
6427
+ return this.unsavedChangesConfirm.confirmIfDirty(this.tabPreviewService.isTabDirty(key), {
6428
+ approveLeaveOnConfirm: false,
6429
+ });
6430
+ }
6431
+ //#endregion
6432
+ //#region ---- Tab Close Navigation ----
6433
+ /**
6434
+ * Navigates away from the active tab before it is removed so canDeactivate runs while the tab is still visible.
6435
+ * Returns false when navigation was cancelled (e.g. unsaved-changes confirmation declined).
6436
+ */
6437
+ async navigateAwayBeforeClosingActiveTab(closedActiveIndex, remainingTabs) {
6438
+ const fallbackIndex = this.resolveFallbackTabIndex(closedActiveIndex, remainingTabs);
6439
+ const fallback = fallbackIndex >= 0 ? remainingTabs[fallbackIndex] : undefined;
6440
+ let targetKey;
6441
+ let targetUrl;
6442
+ if (fallback) {
6443
+ targetKey = fallback.key;
6444
+ targetUrl = fallback.url;
6445
+ }
6446
+ else {
6447
+ await this.ensureHomeTab();
6448
+ const home = this.tabs().find((item) => item.key === AXP_HOME_TAB_KEY);
6449
+ if (!home) {
6450
+ return false;
6451
+ }
6452
+ targetKey = home.key;
6453
+ targetUrl = home.url;
6454
+ }
6455
+ this.suppressNavigationSync = true;
6456
+ try {
6457
+ const navigated = await this.router.navigateByUrl(targetUrl);
6458
+ if (!navigated) {
6459
+ return false;
6460
+ }
6461
+ }
6462
+ finally {
6463
+ this.suppressNavigationSync = false;
6464
+ }
6465
+ this.activeTabKey.set(targetKey);
6466
+ this.touchTab(targetKey);
6467
+ return true;
6468
+ }
6469
+ /** Prefers the tab to the right after removal, then the tab to the left (browser-like). */
6470
+ resolveFallbackTabIndex(closedIndex, remainingTabs) {
6471
+ if (!remainingTabs.length) {
6472
+ return -1;
6473
+ }
6474
+ if (closedIndex < remainingTabs.length) {
6475
+ return closedIndex;
6476
+ }
6477
+ return closedIndex - 1;
6478
+ }
6479
+ //#endregion
6480
+ //#region ---- Route Cache Sync ----
6481
+ /**
6482
+ * Keeps the route cache registry aligned with the current navigation mode and the set of open
6483
+ * tabs. Cached page instances for closed/evicted tabs are destroyed by the registry.
6484
+ */
6485
+ bindRouteCacheSync() {
6486
+ effect(() => {
6487
+ this.routeCacheRegistry.setEnabled(this.effectiveMode() === 'tabular');
6488
+ this.routeCacheRegistry.syncOpenKeys(this.tabs().map((tab) => tab.key), this.tabs().map((tab) => tab.url));
6489
+ });
6490
+ }
6491
+ //#endregion
6492
+ //#region ---- Router Sync ----
6493
+ bindRouterEvents() {
6494
+ this.router.events
6495
+ .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
6496
+ .subscribe((event) => {
6497
+ void this.handleNavigationEnd(event.urlAfterRedirects);
6498
+ });
6499
+ }
6500
+ async handleNavigationEnd(url) {
6501
+ if (this.suppressNavigationSync) {
6502
+ return;
6503
+ }
6504
+ if (this.effectiveMode() === 'simple') {
6505
+ return;
6506
+ }
6507
+ const normalizedUrl = normalizeRouterUrl(url);
6508
+ if (!axpIsTabEligibleUrl(normalizedUrl)) {
6509
+ return;
6510
+ }
6511
+ await this.ensureHomeTab();
6512
+ if (this.isHomePageUrl(normalizedUrl)) {
6513
+ this.activateHomeTabForUrl(normalizedUrl);
6514
+ return;
6515
+ }
6516
+ const key = axpResolveTabKey(this.router, normalizedUrl);
6517
+ const existing = this.findTabForUrl(normalizedUrl);
6518
+ if (existing) {
6519
+ if (existing.key !== key) {
6520
+ this.migrateTabKey(existing.key, key);
6521
+ }
6522
+ this.updateTabUrl(key, normalizedUrl);
6523
+ this.touchTab(key);
6524
+ this.activeTabKey.set(key);
6525
+ this.persistSession();
6526
+ return;
6527
+ }
6528
+ this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
6529
+ this.activeTabKey.set(key);
6530
+ this.persistSession();
6531
+ }
6532
+ //#endregion
6533
+ //#region ---- Settings ----
6534
+ async loadSettings() {
6535
+ const [mode, maxTabs] = await Promise.all([
6536
+ this.settingsService.get(AXPCommonSettings.NavigationMode),
6537
+ this.settingsService.get(AXPCommonSettings.MaxOpenTabs),
6538
+ ]);
6539
+ this.navigationMode.set(mode === 'tabular' ? 'tabular' : 'simple');
6540
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
6541
+ await this.restoreSessionIfNeeded();
6542
+ }
6543
+ bindSettingsChanges() {
6544
+ this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
6545
+ if (event.keys.includes(AXPCommonSettings.NavigationMode)) {
6546
+ const raw = event.values[AXPCommonSettings.NavigationMode];
6547
+ const newMode = raw === 'tabular' ? 'tabular' : 'simple';
6548
+ if (newMode !== this.navigationMode()) {
6549
+ this.navigationMode.set(newMode);
6550
+ if (this.effectiveMode() === 'simple') {
6551
+ this.clearSession();
6552
+ }
6553
+ else {
6554
+ void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
6555
+ }
6556
+ }
6557
+ }
6558
+ if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
6559
+ this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
6560
+ this.enforceMaxTabs();
6561
+ this.persistSession();
6562
+ }
6563
+ });
6564
+ }
6565
+ bindSessionChanges() {
6566
+ this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
6567
+ void this.restoreSessionIfNeeded();
6568
+ });
6569
+ }
6570
+ //#endregion
6571
+ //#region ---- Tab Management ----
6572
+ async ensureHomeTab() {
6573
+ const homePath = this.resolveHomePath();
6574
+ if (!homePath) {
6575
+ return;
6576
+ }
6577
+ const homeUrl = normalizeRouterUrl(homePath);
6578
+ const homeKey = AXP_HOME_TAB_KEY;
6579
+ const existing = this.tabs().find((tab) => tab.key === homeKey);
6580
+ if (existing) {
6581
+ this.tabs.update((items) => items.map((tab) => tab.key === homeKey
6582
+ ? {
6583
+ ...tab,
6584
+ url: homeUrl,
6585
+ title: this.isGenericHomeTitle(tab.title) ? this.resolveHomeTitle() : tab.title,
6586
+ icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
6587
+ }
6588
+ : tab));
6589
+ return;
6590
+ }
6591
+ const now = Date.now();
6592
+ const homeTab = {
6593
+ key: homeKey,
6594
+ url: homeUrl,
6595
+ title: this.resolveHomeTitle(),
6596
+ icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
6597
+ pinned: true,
6598
+ closable: false,
6599
+ openedAt: now,
6600
+ lastActivatedAt: now,
6601
+ };
6602
+ this.tabs.update((items) => this.normalizeTabOrder([homeTab, ...items.filter((tab) => tab.key !== homeKey)]));
6603
+ }
6604
+ insertTab(tab) {
6605
+ if (this.isHomePageUrl(tab.url)) {
6606
+ this.activateHomeTabForUrl(tab.url);
6607
+ return;
6608
+ }
6609
+ this.tabs.update((items) => {
6610
+ const withoutDuplicate = items.filter((item) => {
6611
+ if (item.key === tab.key) {
6612
+ return false;
6613
+ }
6614
+ return axpResolveTabKey(this.router, item.url) !== tab.key;
6615
+ });
6616
+ return this.normalizeTabOrder([...withoutDuplicate, tab]);
6617
+ });
6618
+ this.enforceMaxTabs();
6619
+ }
6620
+ /** Home first; preserves session order for the remaining tabs. */
6621
+ ensureHomeTabFirst(items) {
6622
+ const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
6623
+ const others = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY);
6624
+ return home ? [home, ...others] : items;
6625
+ }
6626
+ getLastPinnedTabIndex(items) {
6627
+ for (let index = items.length - 1; index >= 0; index -= 1) {
6628
+ if (items[index]?.pinned) {
6629
+ return index;
6630
+ }
6631
+ }
6632
+ return 0;
6633
+ }
6634
+ /** Home first, then other pinned tabs, then unpinned tabs (stable by openedAt within each group). */
6635
+ normalizeTabOrder(items) {
6636
+ const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
6637
+ const pinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && tab.pinned);
6638
+ const unpinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && !tab.pinned);
6639
+ const byOpenedAt = (a, b) => a.openedAt - b.openedAt;
6640
+ return [...(home ? [home] : []), ...pinned.sort(byOpenedAt), ...unpinned.sort(byOpenedAt)];
6641
+ }
6642
+ enforceMaxTabs() {
6643
+ const limit = this.maxOpenTabs();
6644
+ let current = this.tabs();
6645
+ while (current.length > limit) {
6646
+ const evictionCandidate = [...current]
6647
+ .filter((tab) => tab.closable && !tab.pinned && tab.key !== this.activeTabKey())
6648
+ .filter((tab) => !this.tabPreviewService.isTabDirty(tab.key))
6649
+ .sort((a, b) => a.lastActivatedAt - b.lastActivatedAt)[0];
6650
+ if (!evictionCandidate) {
6651
+ break;
6652
+ }
6653
+ current = current.filter((tab) => tab.key !== evictionCandidate.key);
6654
+ }
6655
+ this.tabs.set(current);
6656
+ }
6657
+ createTabDescriptor(url, title, keyOverride) {
6658
+ const now = Date.now();
6659
+ const key = keyOverride ?? axpResolveTabKey(this.router, url);
6660
+ return {
6661
+ key,
6662
+ url,
6663
+ title: title ?? this.resolveTabTitle(url),
6664
+ icon: this.resolveTabIcon(url),
6665
+ pinned: false,
6666
+ closable: true,
6667
+ openedAt: now,
6668
+ lastActivatedAt: now,
6669
+ };
6670
+ }
6671
+ /** Finds a tab by scoped key or legacy full-path key for the same URL. */
6672
+ findTabForUrl(url) {
6673
+ if (this.isHomePageUrl(url)) {
6674
+ return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
6675
+ }
6676
+ const resolvedKey = axpResolveTabKey(this.router, url);
6677
+ return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
6678
+ (tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
6679
+ }
6680
+ /** True when the URL is the configured application home page (e.g. dashboard). */
6681
+ isHomePageUrl(url) {
6682
+ const homeUrl = this.resolveHomeUrl();
6683
+ if (!homeUrl) {
6684
+ return false;
6685
+ }
6686
+ return normalizeRouterUrl(url) === homeUrl;
6687
+ }
6688
+ resolveHomeUrl() {
6689
+ const homePath = this.resolveHomePath();
6690
+ if (!homePath) {
6691
+ return null;
6692
+ }
6693
+ return normalizeRouterUrl(homePath);
6694
+ }
6695
+ /** Activates the pinned home tab for a home-page URL instead of opening a duplicate tab. */
6696
+ activateHomeTabForUrl(url) {
6697
+ this.updateTabUrl(AXP_HOME_TAB_KEY, normalizeRouterUrl(url));
6698
+ this.touchTab(AXP_HOME_TAB_KEY);
6699
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
6700
+ this.persistSession();
6701
+ }
6702
+ /** Drops non-home tabs that duplicate the home page URL after session restore. */
6703
+ removeDuplicateHomeUrlTabs(tabs) {
6704
+ const homeUrl = this.resolveHomeUrl();
6705
+ if (!homeUrl) {
6706
+ return tabs;
6707
+ }
6708
+ return tabs.filter((tab) => tab.key === AXP_HOME_TAB_KEY || normalizeRouterUrl(tab.url) !== homeUrl);
6709
+ }
6710
+ /** Rewrites a tab key when route-scope resolution changes (e.g. profile sub-routes). */
6711
+ migrateTabKey(fromKey, toKey) {
6712
+ if (fromKey === toKey) {
6713
+ return;
6714
+ }
6715
+ if (this.activeTabKey() === fromKey) {
6716
+ this.activeTabKey.set(toKey);
6717
+ }
6718
+ this.tabs.update((items) => {
6719
+ const tab = items.find((item) => item.key === fromKey);
6720
+ if (!tab) {
6721
+ return items;
6722
+ }
6723
+ const without = items.filter((item) => item.key !== fromKey && item.key !== toKey);
6724
+ return this.normalizeTabOrder([...without, { ...tab, key: toKey }]);
6725
+ });
6726
+ }
6727
+ /** Collapses restored tabs that share the same scoped route key. */
6728
+ normalizeRestoredTabKeys(tabs) {
6729
+ const byResolvedKey = new Map();
6730
+ for (const tab of tabs) {
6731
+ if (tab.key === AXP_HOME_TAB_KEY) {
6732
+ byResolvedKey.set(AXP_HOME_TAB_KEY, tab);
6733
+ continue;
6734
+ }
6735
+ const resolvedKey = axpResolveTabKey(this.router, tab.url);
6736
+ const normalized = { ...tab, key: resolvedKey };
6737
+ const existing = byResolvedKey.get(resolvedKey);
6738
+ if (!existing || normalized.lastActivatedAt > existing.lastActivatedAt) {
6739
+ byResolvedKey.set(resolvedKey, normalized);
6740
+ }
6741
+ }
6742
+ return [...byResolvedKey.values()];
6743
+ }
6744
+ updateTabUrl(key, url) {
6745
+ this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, url } : tab)));
6746
+ }
6747
+ updateTabTitle(key, title) {
6748
+ this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.title !== title ? { ...tab, title } : tab)));
6749
+ this.persistSession();
6750
+ }
6751
+ updateTabIcon(key, icon) {
6752
+ this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.icon !== icon ? { ...tab, icon } : tab)));
6753
+ this.persistSession();
6754
+ }
6755
+ touchTab(key) {
6756
+ const now = Date.now();
6757
+ this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, lastActivatedAt: now } : tab)));
6758
+ }
6759
+ resolveTabTitle(url, fallback) {
6760
+ const match = axpFindMenuItemByRoute(this.menuService.items(), url);
6761
+ if (match?.item.text) {
6762
+ return match.item.text;
6763
+ }
6764
+ return fallback ?? axpFallbackTabTitle(url);
6765
+ }
6766
+ resolveTabIcon(url, fallback = 'fa-light fa-window') {
6767
+ const match = axpFindMenuItemByRoute(this.menuService.items(), url);
6768
+ if (match?.item.icon?.trim()) {
6769
+ return match.item.icon.trim();
6770
+ }
6771
+ return fallback;
6772
+ }
6773
+ resolveHomePath() {
6774
+ const current = this.homePageService.getCurrent();
6775
+ return current.path ? normalizeRouterUrl(current.path) : '';
6776
+ }
6777
+ resolveHomeTitle() {
6778
+ const current = this.homePageService.getCurrent();
6779
+ return current.title || 'Home';
6780
+ }
6781
+ isGenericHomeTitle(title) {
6782
+ if (title === '@general:terms.interface.navigation.home') {
6783
+ return true;
6784
+ }
6785
+ const homeTitle = this.resolveHomeTitle().trim();
6786
+ return title === homeTitle;
6787
+ }
6788
+ async syncCurrentRouteAsTab() {
6789
+ if (this.effectiveMode() === 'simple') {
6790
+ return;
6791
+ }
6792
+ await this.ensureHomeTab();
6793
+ await this.handleNavigationEnd(this.router.url);
6794
+ }
6795
+ normalizeMaxTabs(value) {
6796
+ const parsed = Number(value);
6797
+ if (!Number.isFinite(parsed)) {
6798
+ return 5;
6799
+ }
6800
+ return Math.min(20, Math.max(2, Math.round(parsed)));
6801
+ }
6802
+ //#endregion
6803
+ //#region ---- Session Persistence ----
6804
+ sessionStorageKey() {
6805
+ const userId = this.sessionService.user?.id;
6806
+ if (!userId) {
6807
+ return null;
6808
+ }
6809
+ return `${SESSION_STORAGE_PREFIX}(${userId})`;
6810
+ }
6811
+ persistSession() {
6812
+ const key = this.sessionStorageKey();
6813
+ if (!key || this.effectiveMode() === 'simple') {
6814
+ return;
5665
6815
  }
5666
- return undefined;
6816
+ const payload = {
6817
+ tabs: cloneDeep(this.tabs()),
6818
+ activeTabKey: this.activeTabKey(),
6819
+ };
6820
+ sessionStorage.setItem(key, JSON.stringify(payload));
5667
6821
  }
5668
- /**
5669
- * Gets the resolved value of a token by its name.
5670
- * @param name The token name.
5671
- * @returns The resolved token value, or undefined if the token is not found.
5672
- */
5673
- async getValue(name) {
5674
- const definition = await this.getDefinition(name);
5675
- if (definition) {
5676
- return await definition.execute();
6822
+ clearSession() {
6823
+ const key = this.sessionStorageKey();
6824
+ if (key) {
6825
+ sessionStorage.removeItem(key);
5677
6826
  }
5678
- return undefined;
6827
+ this.tabs.set([]);
6828
+ this.activeTabKey.set(null);
6829
+ this.tabPreviewService.clearAll();
5679
6830
  }
5680
- //#endregion
5681
- //#region ---- Cache Management ----
5682
- /** Clears the definitions by name cache. */
5683
- clearDefinitionsCache() {
5684
- this.definitionsByName.clear();
6831
+ async restoreSessionIfNeeded() {
6832
+ if (this.effectiveMode() === 'simple') {
6833
+ return;
6834
+ }
6835
+ const storageKey = this.sessionStorageKey();
6836
+ if (!storageKey) {
6837
+ await this.ensureHomeTab();
6838
+ await this.syncCurrentRouteAsTab();
6839
+ return;
6840
+ }
6841
+ const raw = sessionStorage.getItem(storageKey);
6842
+ if (!raw) {
6843
+ await this.ensureHomeTab();
6844
+ await this.syncCurrentRouteAsTab();
6845
+ return;
6846
+ }
6847
+ try {
6848
+ const parsed = JSON.parse(raw);
6849
+ const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
6850
+ this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
6851
+ await this.ensureHomeTab();
6852
+ const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
6853
+ this.activeTabKey.set(activeKey);
6854
+ const activeTab = this.tabs().find((tab) => tab.key === activeKey);
6855
+ if (activeTab && normalizeRouterUrl(this.router.url) !== normalizeRouterUrl(activeTab.url)) {
6856
+ this.suppressNavigationSync = true;
6857
+ try {
6858
+ await this.router.navigateByUrl(activeTab.url);
6859
+ }
6860
+ finally {
6861
+ this.suppressNavigationSync = false;
6862
+ }
6863
+ }
6864
+ }
6865
+ catch {
6866
+ await this.ensureHomeTab();
6867
+ await this.syncCurrentRouteAsTab();
6868
+ }
5685
6869
  }
5686
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5687
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, providedIn: 'root' }); }
6870
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6871
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, providedIn: 'root' }); }
5688
6872
  }
5689
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, decorators: [{
6873
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, decorators: [{
5690
6874
  type: Injectable,
5691
6875
  args: [{
5692
6876
  providedIn: 'root',
5693
6877
  }]
5694
- }] });
5695
-
5696
- class AXPTokenEvaluatorScopeProvider {
5697
- constructor() {
5698
- this.tokenService = inject(AXPTokenDefinitionService);
5699
- }
5700
- async provide(context) {
5701
- context.addScope('tokens', {
5702
- get: async (name) => {
5703
- return await this.tokenService.getValue(name);
5704
- }
5705
- });
5706
- }
5707
- }
5708
-
5709
- //#endregion
5710
- //#region ---- App Path Navigation ----
5711
- /**
5712
- * Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
5713
- * Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
5714
- */
5715
- function axpParseAppPath(path) {
5716
- const trimmed = path.trim();
5717
- const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
5718
- const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
5719
- const [pathPart, fragment] = pathAndFragment.split('#');
5720
- const segments = pathPart.split('/').filter(Boolean);
5721
- const queryParams = queryString
5722
- ? queryString.split('&').reduce((params, pair) => {
5723
- const [key, value = ''] = pair.split('=');
5724
- if (key) {
5725
- params[decodeURIComponent(key)] = decodeURIComponent(value);
5726
- }
5727
- return params;
5728
- }, {})
5729
- : undefined;
5730
- return {
5731
- segments,
5732
- queryParams,
5733
- fragment: fragment || undefined,
5734
- };
5735
- }
5736
- /** Navigates using explicit path segments (same pattern as entity preload filters). */
5737
- function axpNavigateAppPath(router, path, extras) {
5738
- const { segments, queryParams, fragment } = axpParseAppPath(path);
5739
- return router.navigate(segments, {
5740
- ...extras,
5741
- queryParams,
5742
- fragment,
5743
- });
5744
- }
5745
- /** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
5746
- function axpIsEntityListPath(url) {
5747
- const path = url.split('?')[0].split('#')[0];
5748
- return /\/e\/[^/]+\/list$/.test(path);
5749
- }
5750
- /** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
5751
- function axpIsEntityListRoute(router) {
5752
- return axpIsEntityListPath(router.url);
5753
- }
5754
- /** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
5755
- function axpIsEntityDetailsPath(url) {
5756
- const path = url.split('?')[0].split('#')[0];
5757
- return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
5758
- }
5759
- /** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
5760
- function axpIsEntityDetailsViewRoute(router) {
5761
- return axpIsEntityDetailsPath(router.url);
5762
- }
5763
- /**
5764
- * Merges query params on the current entity list route without pushing history.
5765
- * Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
5766
- */
5767
- function axpReplaceEntityListRouteQueryParams(router, queryParams) {
5768
- const url = router.url;
5769
- if (!axpIsEntityListPath(url)) {
5770
- return;
5771
- }
5772
- const pathOnly = url.split('?')[0].split('#')[0];
5773
- const { segments } = axpParseAppPath(pathOnly);
5774
- void router.navigate(segments, {
5775
- queryParams,
5776
- queryParamsHandling: 'merge',
5777
- replaceUrl: true,
5778
- });
5779
- }
5780
- //#endregion
6878
+ }], ctorParameters: () => [] });
5781
6879
 
5782
6880
  //TODO Loading, Redirect, Drawer, Show toast
5783
6881
  const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
@@ -5787,6 +6885,7 @@ class AXPWorkflowNavigateAction extends AXPWorkflowAction {
5787
6885
  constructor() {
5788
6886
  super(...arguments);
5789
6887
  this.router = inject(Router);
6888
+ this.tabNavigation = inject(AXPTabNavigationService);
5790
6889
  }
5791
6890
  async execute(context) {
5792
6891
  const payload = context.getVariable('payload');
@@ -5802,7 +6901,7 @@ class AXPWorkflowNavigateAction extends AXPWorkflowAction {
5802
6901
  window.open(path, '_blank');
5803
6902
  return;
5804
6903
  }
5805
- await axpNavigateAppPath(this.router, path, payload.extras);
6904
+ await this.tabNavigation.navigate(path, payload.extras);
5806
6905
  }
5807
6906
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
5808
6907
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
@@ -5869,7 +6968,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
5869
6968
  class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
5870
6969
  constructor() {
5871
6970
  super(...arguments);
5872
- this.router = inject(Router);
6971
+ this.tabNavigation = inject(AXPTabNavigationService);
5873
6972
  }
5874
6973
  async execute(context) {
5875
6974
  const command = context.getVariable();
@@ -5882,7 +6981,7 @@ class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
5882
6981
  window.open(path, '_blank');
5883
6982
  }
5884
6983
  else {
5885
- await axpNavigateAppPath(this.router, path, command.options.extras);
6984
+ await this.tabNavigation.navigate(path, command.options.extras);
5886
6985
  }
5887
6986
  }
5888
6987
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRouterNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -5956,13 +7055,14 @@ class AXPCommonModule {
5956
7055
  /**
5957
7056
  * @ignore
5958
7057
  */
5959
- constructor(instances, _keyboardShortcutsHelp) {
7058
+ constructor(instances, _keyboardShortcutsHelp, formatterRegistry) {
7059
+ formatterRegistry.register(AXPTranslateFormatter);
5960
7060
  instances.forEach((f) => {
5961
7061
  f();
5962
7062
  });
5963
7063
  }
5964
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, deps: [{ token: 'AXPCommonModuleFactory' }, { token: AXPKeyboardShortcutsHelpService }], target: i0.ɵɵFactoryTarget.NgModule }); }
5965
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, imports: [i2$1.AXPComponentSlotModule, i3$1.AXPWorkflowModule, AXPopupModule,
7064
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, deps: [{ token: 'AXPCommonModuleFactory' }, { token: AXPKeyboardShortcutsHelpService }, { token: i2$1.AXFormatterRegistryService }], target: i0.ɵɵFactoryTarget.NgModule }); }
7065
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, imports: [i3$1.AXPComponentSlotModule, i4$2.AXPWorkflowModule, AXPopupModule,
5966
7066
  AXDateTimeModule,
5967
7067
  AXToastModule,
5968
7068
  AXPModuleManifestModule], exports: [RouterModule] }); }
@@ -6009,7 +7109,7 @@ class AXPCommonModule {
6009
7109
  provide: AXP_SETTING_DEFINITION_PROVIDER,
6010
7110
  useFactory: async () => {
6011
7111
  const injector = inject(Injector);
6012
- const provider = (await import('./acorex-platform-common-common-settings.provider-Ytey9uhY.mjs')).AXPCommonSettingProvider;
7112
+ const provider = (await import('./acorex-platform-common-common-settings.provider-VfmZct9H.mjs')).AXPCommonSettingProvider;
6013
7113
  return new provider(injector);
6014
7114
  },
6015
7115
  multi: true,
@@ -6110,7 +7210,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6110
7210
  provide: AXP_SETTING_DEFINITION_PROVIDER,
6111
7211
  useFactory: async () => {
6112
7212
  const injector = inject(Injector);
6113
- const provider = (await import('./acorex-platform-common-common-settings.provider-Ytey9uhY.mjs')).AXPCommonSettingProvider;
7213
+ const provider = (await import('./acorex-platform-common-common-settings.provider-VfmZct9H.mjs')).AXPCommonSettingProvider;
6114
7214
  return new provider(injector);
6115
7215
  },
6116
7216
  multi: true,
@@ -6120,7 +7220,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6120
7220
  }], ctorParameters: () => [{ type: undefined, decorators: [{
6121
7221
  type: Inject,
6122
7222
  args: ['AXPCommonModuleFactory']
6123
- }] }, { type: AXPKeyboardShortcutsHelpService }] });
7223
+ }] }, { type: AXPKeyboardShortcutsHelpService }, { type: i2$1.AXFormatterRegistryService }] });
6124
7224
 
6125
7225
  const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
6126
7226
  const AXP_PLATFORM_CONFIG_TOKEN = new InjectionToken('AXP_PLATFORM_CONFIG_TOKEN', {
@@ -6327,6 +7427,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6327
7427
  args: [{ providedIn: 'root' }]
6328
7428
  }] });
6329
7429
 
7430
+ const AXP_INTEGRATION_PROVIDER = new InjectionToken('AXP_INTEGRATION_PROVIDER', {
7431
+ providedIn: 'root',
7432
+ factory: () => [],
7433
+ });
7434
+ const AXP_INTEGRATION_RUNTIME_PROVIDER = new InjectionToken('AXP_INTEGRATION_RUNTIME_PROVIDER', {
7435
+ providedIn: 'root',
7436
+ factory: () => [],
7437
+ });
7438
+ //#endregion
7439
+
7440
+ //#region ---- Integration provider context ----
7441
+ class AXPIntegrationProviderContext {
7442
+ constructor() {
7443
+ //#region ---- State ----
7444
+ this.definitions = new Map();
7445
+ //#endregion
7446
+ }
7447
+ //#endregion
7448
+ //#region ---- Builder API ----
7449
+ /**
7450
+ * Registers a provider definition. Throws when duplicate ids are registered.
7451
+ */
7452
+ register(definition) {
7453
+ if (this.definitions.has(definition.id)) {
7454
+ throw new Error(`[AXPIntegrationProviderContext] Duplicate integration provider id '${definition.id}'.`);
7455
+ }
7456
+ this.definitions.set(definition.id, definition);
7457
+ }
7458
+ //#endregion
7459
+ //#region ---- Accessors ----
7460
+ getDefinitions() {
7461
+ return this.definitions;
7462
+ }
7463
+ }
7464
+ //#endregion
7465
+
7466
+ //#region ---- Integration provider registry ----
7467
+ class AXPIntegrationProviderRegistryService {
7468
+ constructor() {
7469
+ //#region ---- Dependencies ----
7470
+ this.providers = inject(AXP_INTEGRATION_PROVIDER, { optional: true });
7471
+ //#endregion
7472
+ //#region ---- State ----
7473
+ this.cache = null;
7474
+ }
7475
+ //#endregion
7476
+ //#region ---- Public API ----
7477
+ async list() {
7478
+ const map = await this.load();
7479
+ return Array.from(map.values());
7480
+ }
7481
+ async get(id) {
7482
+ const map = await this.load();
7483
+ return map.get(id) ?? null;
7484
+ }
7485
+ async getOrThrow(id) {
7486
+ const definition = await this.get(id);
7487
+ if (!definition) {
7488
+ throw new Error(`[AXPIntegrationProviderRegistry] Unknown integration provider '${id}'.`);
7489
+ }
7490
+ return definition;
7491
+ }
7492
+ clearCache() {
7493
+ this.cache = null;
7494
+ }
7495
+ //#endregion
7496
+ //#region ---- Private Methods ----
7497
+ async load() {
7498
+ if (this.cache) {
7499
+ return this.cache;
7500
+ }
7501
+ const context = new AXPIntegrationProviderContext();
7502
+ const providerList = this.providers ?? [];
7503
+ for (const provider of providerList) {
7504
+ const resolved = provider instanceof Promise ? await provider : provider;
7505
+ await resolved.provide(context);
7506
+ }
7507
+ this.cache = new Map(context.getDefinitions());
7508
+ return this.cache;
7509
+ }
7510
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7511
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, providedIn: 'root' }); }
7512
+ }
7513
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, decorators: [{
7514
+ type: Injectable,
7515
+ args: [{ providedIn: 'root' }]
7516
+ }] });
7517
+
7518
+ const AXP_INTEGRATION_SECRET_STORE = new InjectionToken('AXP_INTEGRATION_SECRET_STORE');
7519
+ class AXPIntegrationRuntimeService {
7520
+ }
7521
+ class AXPIntegrationOAuthService {
7522
+ }
7523
+ const AXP_INTEGRATION_OAUTH_SERVICE = new InjectionToken('AXP_INTEGRATION_OAUTH_SERVICE');
7524
+ //#endregion
7525
+
7526
+ //#region ---- Integration barrel ----
7527
+ //#endregion
7528
+
6330
7529
  class AXPLockService {
6331
7530
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6332
7531
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService }); }
@@ -6335,18 +7534,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6335
7534
  type: Injectable
6336
7535
  }] });
6337
7536
 
6338
- /**
6339
- * Status Definition Types
6340
- * Defines the structure for status definitions and their transitions
6341
- */
6342
- /**
6343
- * Abstract class for status providers
6344
- * Similar to AXPTaskBadgeProvider pattern
6345
- */
6346
- class AXPStatusProvider {
6347
- }
6348
- //#endregion
6349
-
6350
7537
  //#region ---- Injection Token ----
6351
7538
  /**
6352
7539
  * Injection token for status providers
@@ -6558,356 +7745,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6558
7745
  args: [{ providedIn: 'root' }]
6559
7746
  }] });
6560
7747
 
6561
- /**
6562
- * Standard system status types
6563
- * These are the predefined status values used across the platform
6564
- */
6565
- var AXPSystemStatusType;
6566
- (function (AXPSystemStatusType) {
6567
- AXPSystemStatusType["Todo"] = "todo";
6568
- AXPSystemStatusType["InProgress"] = "in-progress";
6569
- AXPSystemStatusType["InReview"] = "in-review";
6570
- AXPSystemStatusType["Open"] = "open";
6571
- AXPSystemStatusType["Closed"] = "closed";
6572
- AXPSystemStatusType["Blocked"] = "blocked";
6573
- AXPSystemStatusType["Done"] = "done";
6574
- AXPSystemStatusType["Cancelled"] = "cancelled";
6575
- AXPSystemStatusType["Draft"] = "draft";
6576
- AXPSystemStatusType["PendingReview"] = "pending-review";
6577
- AXPSystemStatusType["WaitingSignOff"] = "waiting-sign-off";
6578
- AXPSystemStatusType["WaitingApproval"] = "waiting-approval";
6579
- AXPSystemStatusType["Approved"] = "approved";
6580
- AXPSystemStatusType["Published"] = "published";
6581
- AXPSystemStatusType["Archived"] = "archived";
6582
- AXPSystemStatusType["Rejected"] = "rejected";
6583
- AXPSystemStatusType["Active"] = "active";
6584
- AXPSystemStatusType["Inactive"] = "inactive";
6585
- AXPSystemStatusType["Pending"] = "pending";
6586
- AXPSystemStatusType["Completed"] = "completed";
6587
- AXPSystemStatusType["Suspended"] = "suspended";
6588
- AXPSystemStatusType["Failed"] = "failed";
6589
- AXPSystemStatusType["Review"] = "review";
6590
- AXPSystemStatusType["Expired"] = "expired";
6591
- })(AXPSystemStatusType || (AXPSystemStatusType = {}));
6592
- const i18n = (key) => `@general:statuses.${key}`;
6593
- /**
6594
- * Standard system status definitions
6595
- * Provides consistent status definitions across the platform
6596
- * Uses AXPStatusDefinition interface from status-definition.types.ts
6597
- */
6598
- const AXPSystemStatuses = Object.freeze({
6599
- Open: {
6600
- name: AXPSystemStatusType.Open,
6601
- title: i18n('open.title'),
6602
- icon: 'fa-light fa-circle',
6603
- color: 'secondary',
6604
- description: i18n('open.description'),
6605
- order: 1,
6606
- isInitial: true,
6607
- isFinal: false,
6608
- },
6609
- Closed: {
6610
- name: AXPSystemStatusType.Closed,
6611
- title: i18n('closed.title'),
6612
- icon: 'fa-light fa-circle',
6613
- color: 'danger',
6614
- description: i18n('closed.description'),
6615
- order: 2,
6616
- isInitial: false,
6617
- isFinal: true,
6618
- },
6619
- Todo: {
6620
- name: AXPSystemStatusType.Todo,
6621
- title: i18n('todo.title'),
6622
- icon: 'fa-light fa-circle',
6623
- color: 'secondary',
6624
- description: i18n('todo.description'),
6625
- order: 1,
6626
- isInitial: true,
6627
- isFinal: false,
6628
- },
6629
- InProgress: {
6630
- name: AXPSystemStatusType.InProgress,
6631
- title: i18n('in-progress.title'),
6632
- icon: 'fa-light fa-play',
6633
- color: 'info',
6634
- description: i18n('in-progress.description'),
6635
- order: 2,
6636
- isInitial: false,
6637
- isFinal: false,
6638
- },
6639
- InReview: {
6640
- name: AXPSystemStatusType.InReview,
6641
- title: i18n('in-review.title'),
6642
- icon: 'fa-light fa-eye',
6643
- color: 'info',
6644
- description: i18n('in-review.description'),
6645
- order: 3,
6646
- isInitial: false,
6647
- isFinal: false,
6648
- },
6649
- Blocked: {
6650
- name: AXPSystemStatusType.Blocked,
6651
- title: i18n('blocked.title'),
6652
- icon: 'fa-light fa-ban',
6653
- color: 'danger',
6654
- description: i18n('blocked.description'),
6655
- order: 4,
6656
- isInitial: false,
6657
- isFinal: false,
6658
- },
6659
- Done: {
6660
- name: AXPSystemStatusType.Done,
6661
- title: i18n('done.title'),
6662
- icon: 'fa-light fa-check-circle',
6663
- color: 'success',
6664
- description: i18n('done.description'),
6665
- order: 5,
6666
- isInitial: false,
6667
- isFinal: true,
6668
- },
6669
- Cancelled: {
6670
- name: AXPSystemStatusType.Cancelled,
6671
- title: i18n('cancelled.title'),
6672
- icon: 'fa-light fa-times-circle',
6673
- color: 'neutral',
6674
- description: i18n('cancelled.description'),
6675
- order: 6,
6676
- isInitial: false,
6677
- isFinal: true,
6678
- },
6679
- Draft: {
6680
- name: AXPSystemStatusType.Draft,
6681
- title: i18n('draft.title'),
6682
- icon: 'fa-light fa-file-pen',
6683
- color: 'neutral',
6684
- description: i18n('draft.description'),
6685
- order: 1,
6686
- isInitial: true,
6687
- isFinal: false,
6688
- },
6689
- PendingReview: {
6690
- name: AXPSystemStatusType.PendingReview,
6691
- title: i18n('pending-review.title'),
6692
- icon: 'fa-light fa-clock',
6693
- color: 'warning',
6694
- description: i18n('pending-review.description'),
6695
- order: 2,
6696
- isInitial: false,
6697
- isFinal: false,
6698
- },
6699
- WaitingSignOff: {
6700
- name: AXPSystemStatusType.WaitingSignOff,
6701
- title: i18n('waiting-sign-off.title'),
6702
- icon: 'fa-light fa-clock',
6703
- color: 'warning',
6704
- description: i18n('waiting-sign-off.description'),
6705
- order: 3,
6706
- isInitial: false,
6707
- isFinal: false,
6708
- },
6709
- WaitingApproval: {
6710
- name: AXPSystemStatusType.WaitingApproval,
6711
- title: i18n('waiting-approval.title'),
6712
- icon: 'fa-light fa-clock',
6713
- color: 'warning',
6714
- description: i18n('waiting-approval.description'),
6715
- order: 1,
6716
- isInitial: true,
6717
- isFinal: false,
6718
- },
6719
- Approved: {
6720
- name: AXPSystemStatusType.Approved,
6721
- title: i18n('approved.title'),
6722
- icon: 'fa-light fa-check-circle',
6723
- color: 'success',
6724
- description: i18n('approved.description'),
6725
- order: 3,
6726
- isInitial: false,
6727
- isFinal: false,
6728
- },
6729
- Published: {
6730
- name: AXPSystemStatusType.Published,
6731
- title: i18n('published.title'),
6732
- icon: 'fa-light fa-globe',
6733
- color: 'primary',
6734
- description: i18n('published.description'),
6735
- order: 4,
6736
- isInitial: false,
6737
- isFinal: false,
6738
- },
6739
- Archived: {
6740
- name: AXPSystemStatusType.Archived,
6741
- title: i18n('archived.title'),
6742
- icon: 'fa-light fa-archive',
6743
- color: 'danger',
6744
- description: i18n('archived.description'),
6745
- order: 5,
6746
- isInitial: false,
6747
- isFinal: true,
6748
- },
6749
- Rejected: {
6750
- name: AXPSystemStatusType.Rejected,
6751
- title: i18n('rejected.title'),
6752
- icon: 'fa-light fa-times-circle',
6753
- color: 'danger',
6754
- description: i18n('rejected.description'),
6755
- order: 6,
6756
- isInitial: false,
6757
- isFinal: true,
6758
- },
6759
- Active: {
6760
- name: AXPSystemStatusType.Active,
6761
- title: i18n('active.title'),
6762
- icon: 'fa-light fa-check-circle',
6763
- color: 'success',
6764
- description: i18n('active.description'),
6765
- order: 1,
6766
- isInitial: false,
6767
- isFinal: false,
6768
- },
6769
- Inactive: {
6770
- name: AXPSystemStatusType.Inactive,
6771
- title: i18n('inactive.title'),
6772
- icon: 'fa-light fa-circle',
6773
- color: 'secondary',
6774
- description: i18n('inactive.description'),
6775
- order: 2,
6776
- isInitial: false,
6777
- isFinal: false,
6778
- },
6779
- Pending: {
6780
- name: AXPSystemStatusType.Pending,
6781
- title: i18n('pending.title'),
6782
- icon: 'fa-light fa-clock',
6783
- color: 'warning',
6784
- description: i18n('pending.description'),
6785
- order: 1,
6786
- isInitial: true,
6787
- isFinal: false,
6788
- },
6789
- Completed: {
6790
- name: AXPSystemStatusType.Completed,
6791
- title: i18n('completed.title'),
6792
- icon: 'fa-light fa-check-circle',
6793
- color: 'success',
6794
- description: i18n('completed.description'),
6795
- order: 10,
6796
- isInitial: false,
6797
- isFinal: true,
6798
- },
6799
- Suspended: {
6800
- name: AXPSystemStatusType.Suspended,
6801
- title: i18n('suspended.title'),
6802
- icon: 'fa-light fa-pause-circle',
6803
- color: 'neutral',
6804
- description: i18n('suspended.description'),
6805
- order: 5,
6806
- isInitial: false,
6807
- isFinal: false,
6808
- },
6809
- Failed: {
6810
- name: AXPSystemStatusType.Failed,
6811
- title: i18n('failed.title'),
6812
- icon: 'fa-light fa-xmark-circle',
6813
- color: 'danger',
6814
- description: i18n('failed.description'),
6815
- order: 10,
6816
- isInitial: false,
6817
- isFinal: true,
6818
- },
6819
- Review: {
6820
- name: AXPSystemStatusType.Review,
6821
- title: i18n('review.title'),
6822
- icon: 'fa-light fa-eye',
6823
- color: 'info',
6824
- description: i18n('review.description'),
6825
- order: 3,
6826
- },
6827
- Expired: {
6828
- name: AXPSystemStatusType.Expired,
6829
- title: i18n('expired.title'),
6830
- icon: 'fa-light fa-clock',
6831
- color: 'danger',
6832
- description: i18n('expired.description'),
6833
- order: 10,
6834
- isInitial: false,
6835
- isFinal: true,
6836
- },
6837
- });
6838
- /**
6839
- * Get system status definition by type
6840
- * @param type - Status type
6841
- * @returns System status definition or undefined
6842
- */
6843
- function getSystemStatus(type) {
6844
- return Object.values(AXPSystemStatuses).find(status => status.name === type);
6845
- }
6846
- /**
6847
- * Resolves the visual appearance (color and icon) for status
6848
- * using the system statuses from the core module.
6849
- * @param statusType - Status type string
6850
- * @returns Color and icon for the status
6851
- */
6852
- function resolveStatusLook(statusType) {
6853
- // Try to get system status first
6854
- const systemStatusType = statusType;
6855
- if (systemStatusType) {
6856
- const systemStatus = getSystemStatus(systemStatusType);
6857
- if (systemStatus) {
6858
- // Convert hex color to semantic color type if needed
6859
- // For now, return as-is and let the UI handle it
6860
- return {
6861
- color: systemStatus.color,
6862
- icon: systemStatus.icon || ''
6863
- };
6864
- }
6865
- }
6866
- // Fallback for unknown statuses
6867
- return {
6868
- color: 'secondary',
6869
- icon: ''
6870
- };
6871
- }
6872
- /**
6873
- * Resolves the complete status information (title, description, icon, color) for a status
6874
- * using the system statuses from the core module.
6875
- * @param statusType - Status type string
6876
- * @returns Complete status information as AXPStatusDefinition
6877
- */
6878
- function getStatusInfo(statusType) {
6879
- const systemStatusType = statusType;
6880
- if (systemStatusType) {
6881
- const systemStatus = getSystemStatus(systemStatusType);
6882
- if (systemStatus) {
6883
- return systemStatus;
6884
- }
6885
- }
6886
- // Fallback for unknown statuses
6887
- return {
6888
- name: statusType,
6889
- title: statusType,
6890
- description: `Status: ${statusType}`,
6891
- icon: '',
6892
- color: 'secondary'
6893
- };
6894
- }
6895
- /**
6896
- * Get system status definition directly (no conversion needed since it's already AXPStatusDefinition)
6897
- * @param statusType - Status type
6898
- * @param overrides - Optional overrides for the status definition
6899
- * @returns AXPStatusDefinition
6900
- */
6901
- function systemStatusToDefinition(statusType, overrides) {
6902
- const systemStatus = getSystemStatus(statusType);
6903
- if (!systemStatus) {
6904
- throw new Error(`System status ${statusType} not found`);
6905
- }
6906
- return {
6907
- ...systemStatus,
6908
- ...overrides,
6909
- };
6910
- }
7748
+ //#region ---- Types ----
7749
+ //#endregion
6911
7750
 
6912
7751
  class AXPClipBoardService {
6913
7752
  constructor() {
@@ -7590,5 +8429,5 @@ class AXPVersioningService {
7590
8429
  * Generated bundle index. Do not edit.
7591
8430
  */
7592
8431
 
7593
- export { ALL_DEFAULT_OPERATORS, AXMWorkflowErrorHandler, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDebugService, AXPDefaultMultiLanguageConfigService, AXPDialogConfirmAction, AXPEntityCommandScope, AXPEntityQueryType, AXPEntityType, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLockService, AXPMenuItemsDataSourceDefinition, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateWorkflow, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPlatformCommonDataSourceKeys, AXPPlatformDefaultConfigs, AXPRedirectEvent, AXPRefreshEvent, AXPRegionalCalendarService, AXPRegionalSetting, AXPRelationshipCardinality, AXPRelationshipKind, AXPReloadAction, AXPReloadEvent, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsEvaluatorScopeProvider, AXPSettingsService, AXPStatusDefinitionProviderService, AXPStatusProvider, AXPStickyDirective, AXPSystemStatusType, AXPSystemStatuses, AXPToastAction, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPVersioningService, AXPWorkflowNavigateAction, AXPWorkflowRouterNavigateAction, AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES, AXP_APP_VERSION_PROVIDER, AXP_BACKGROUND_OPERATION_ENTITY_STORAGE, AXP_BACKGROUND_OPERATION_HANDLERS, AXP_BACKGROUND_OPERATION_PROVIDERS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_LOCALE_MANAGEMENT_PORT, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXVChangeType, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LAYOUT_SINGLE_TITLE_PATH, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, ENVIRONMENT, EQ_OPER, GTE_OPER, GT_OPER, IN_OPER, IS_EMPTY_OPER, IS_NOT_EMPTY_OPER, LTE_OPER, LT_OPER, MULTILANGUAGE_CAPABLE_WIDGET_TYPES, NOT_CONTAINS_OPER, NOT_EQ_OPER, NUMBER_OPERATORS, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationToEntityModel, axpBuildBackgroundOperation, axpCoerceBackgroundOperationDate, axpCreateBackgroundOperationId, axpFindMenuItemByRoute, axpIsActiveBackgroundOperationStatus, axpIsCompletedBackgroundOperationStatus, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpMenuPathsMatch, axpNavigateAppPath, axpParseAppPath, axpRedirectToNotFound, axpRedirectToUnauthorized, axpReplaceEntityListRouteQueryParams, backgroundOperationEntityFactory, buildEntitySearchTitleContext, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createAllQueryView, createBackgroundOperationStore, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createQueryView, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, findManualPresetIdByOperation, formatEntityRowDisplayTitle, getEntityInfo, getStatusInfo, getSystemStatus, inferSettingCoercionKind, isCardFieldBadgeDisplay, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, normalizeEntityDisplayTemplate, normalizePositiveNumberOption, peelSettingValueKey, printGraphicFromElement, provideDynamicHomePage, provideMenuMiddleware, resolveCardFieldBadgeColor, resolveDateFilterPresets, resolveDefaultMasterListLayout, resolveEnabledMasterListLayouts, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntityRowTitleTemplate, resolveEntitySearchTitleFallback, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, resolveStatusLook, sanitizeResolvedEntityDisplayTitle, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, systemStatusToDefinition, triggerBrowserDownload, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
8432
+ export { ALL_DEFAULT_OPERATORS, AXMWorkflowErrorHandler, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDebugService, AXPDefaultMultiLanguageConfigService, AXPDialogConfirmAction, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLockService, AXPMenuItemsDataSourceDefinition, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateWorkflow, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPlatformCommonDataSourceKeys, AXPPlatformDefaultConfigs, AXPRedirectEvent, AXPRefreshEvent, AXPRegionalCalendarService, AXPReloadAction, AXPReloadEvent, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsEvaluatorScopeProvider, AXPSettingsService, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPToastAction, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPVersioningService, AXPWorkflowNavigateAction, AXPWorkflowRouterNavigateAction, AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES, AXP_APP_VERSION_PROVIDER, AXP_BACKGROUND_OPERATION_ENTITY_STORAGE, AXP_BACKGROUND_OPERATION_HANDLERS, AXP_BACKGROUND_OPERATION_PROVIDERS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_INTEGRATION_OAUTH_SERVICE, AXP_INTEGRATION_PROVIDER, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_SECRET_STORE, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_LOCALE_MANAGEMENT_PORT, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXVChangeType, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENVIRONMENT, EQ_OPER, GTE_OPER, GT_OPER, IN_OPER, IS_EMPTY_OPER, IS_NOT_EMPTY_OPER, LTE_OPER, LT_OPER, MULTILANGUAGE_CAPABLE_WIDGET_TYPES, NOT_CONTAINS_OPER, NOT_EQ_OPER, NUMBER_OPERATORS, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationToEntityModel, axpBuildBackgroundOperation, axpBuildTabKey, axpCoerceBackgroundOperationDate, axpCreateBackgroundOperationId, axpFallbackTabTitle, axpFindMenuItemByRoute, axpGetLeafRouteSnapshot, axpHasTabScope, axpIsActiveBackgroundOperationStatus, axpIsCompletedBackgroundOperationStatus, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsTabEligibleUrl, axpMenuPathsMatch, axpNavigateAppPath, axpParseAppPath, axpPathFromSnapshot, axpRedirectToNotFound, axpRedirectToUnauthorized, axpReplaceEntityListRouteQueryParams, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, backgroundOperationEntityFactory, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, findManualPresetIdByOperation, formatEntityRowDisplayTitle, inferSettingCoercionKind, normalizePositiveNumberOption, normalizeRouterUrl, peelSettingValueKey, printGraphicFromElement, provideDynamicHomePage, provideMenuMiddleware, resolveDateFilterPresets, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, triggerBrowserDownload, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
7594
8433
  //# sourceMappingURL=acorex-platform-common.mjs.map