@acorex/platform 21.0.0-next.75 → 21.0.0-next.76

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 (40) hide show
  1. package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-ChoT4m4k.mjs} +42 -1
  2. package/fesm2022/acorex-platform-common-common-settings.provider-ChoT4m4k.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-common.mjs +1471 -606
  4. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-contracts.mjs +1330 -1
  6. package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-core.mjs +208 -5
  8. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-layout-entity.mjs +595 -439
  11. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-views.mjs +372 -171
  13. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-widget-core.mjs +3 -103
  15. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
  17. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
  18. package/fesm2022/acorex-platform-layout-widgets.mjs +11 -6
  19. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  20. 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
  21. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
  22. package/fesm2022/acorex-platform-themes-default.mjs +643 -52
  23. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-workflow.mjs +5 -8
  25. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  26. package/package.json +1 -1
  27. package/types/acorex-platform-common.d.ts +337 -1056
  28. package/types/acorex-platform-contracts.d.ts +1679 -4
  29. package/types/acorex-platform-core.d.ts +92 -181
  30. package/types/acorex-platform-layout-components.d.ts +2 -1
  31. package/types/acorex-platform-layout-entity.d.ts +68 -176
  32. package/types/acorex-platform-layout-views.d.ts +39 -5
  33. package/types/acorex-platform-layout-widget-core.d.ts +2 -3
  34. package/types/acorex-platform-layout-widgets.d.ts +11 -4
  35. package/types/acorex-platform-themes-default.d.ts +120 -57
  36. package/types/acorex-platform-workflow.d.ts +5 -18
  37. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
  38. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
  39. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
  40. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
@@ -1,15 +1,17 @@
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 { AXPRegionalSetting } from '@acorex/platform/contracts';
4
+ export * from '@acorex/platform/contracts';
4
5
  import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, RouterModule } from '@angular/router';
5
6
  import { AXPSessionService, AXPSessionStatus, AXPAuthGuard } from '@acorex/platform/auth';
6
7
  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';
8
+ import * as i3$1 from '@acorex/platform/core';
9
+ import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, AXPBroadcastEventService, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, AXPSystemActionType, normalizeExpressionTemplate, isLocaleStringMap, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPDeviceService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
10
+ import { merge, sortBy, cloneDeep, get, isNumber, isNil, isObject, isString, isPlainObject, omit, defaultTo } from 'lodash-es';
9
11
  import { AXCalendarService, AXDateTimeModule } from '@acorex/core/date-time';
10
12
  import { AXDialogService } from '@acorex/components/dialog';
11
13
  import * as i5 from '@acorex/core/translation';
12
- import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
14
+ import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString } from '@acorex/core/translation';
13
15
  import * as i4$1 from '@angular/common';
14
16
  import { Location, CommonModule, AsyncPipe, DOCUMENT } from '@angular/common';
15
17
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
@@ -24,13 +26,15 @@ import * as i3 from '@acorex/components/progress-bar';
24
26
  import { AXProgressBarModule } from '@acorex/components/progress-bar';
25
27
  import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
26
28
  import { AXToastService, AXToastModule } from '@acorex/components/toast';
27
- import * as i3$1 from '@acorex/platform/workflow';
29
+ import * as i2$1 from '@acorex/core/format';
30
+ import { AXFormatService } from '@acorex/core/format';
31
+ import * as i4$2 from '@acorex/platform/workflow';
28
32
  import { AXPWorkflowService, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowError, AXPWorkflowModule } from '@acorex/platform/workflow';
29
33
  import { AXKBDComponent, AXKBDItemComponent } from '@acorex/components/kbd';
30
34
  import { AXDataSource } from '@acorex/cdk/common';
31
35
  import { AXPCommandExecutor, AXPCommandService } from '@acorex/platform/runtime';
32
36
  import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
33
- import { AXFormatService } from '@acorex/core/format';
37
+ import { filter } from 'rxjs/operators';
34
38
  import { AXFileService } from '@acorex/core/file';
35
39
  import { AXCheckBoxModule } from '@acorex/components/check-box';
36
40
  import * as i6 from '@acorex/components/color-box';
@@ -164,110 +168,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
164
168
  }]
165
169
  }] });
166
170
 
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 = {}));
171
+ /**
172
+ * Entity definition types, query views, and related helpers.
173
+ * Canonical definitions live in @acorex/platform/contracts.
174
+ */
271
175
 
272
176
  const EQ_OPER = { name: 'equal', title: '@general:terms.operators.equal', hasValue: true };
273
177
  const NOT_EQ_OPER = { name: 'notEqual', title: '@general:terms.operators.not-equal', hasValue: true };
@@ -472,38 +376,6 @@ const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY'
472
376
  factory: () => 'home'
473
377
  });
474
378
 
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
379
  class AXPSettingDefinitionProviderContext {
508
380
  constructor() {
509
381
  this.rootGroups = [];
@@ -601,6 +473,7 @@ class AXPSettingDefinitionSectionBuilder {
601
473
  isRequired: setting.isRequired ?? false,
602
474
  isInherited: setting.isInherited ?? false,
603
475
  isEncrypted: setting.isEncrypted ?? false,
476
+ requiresPageRefresh: setting.requiresPageRefresh ?? false,
604
477
  defaultValue: setting.defaultValue,
605
478
  scope: setting.scope ?? 'P',
606
479
  layout: setting.widget.layout,
@@ -735,6 +608,26 @@ class AXPSettingDefinitionProviderService {
735
608
  collect(this.cache);
736
609
  return scopes;
737
610
  }
611
+ async requiresPageRefreshByKey() {
612
+ const refreshByKey = {};
613
+ const collect = (groups) => {
614
+ groups.forEach((group) => {
615
+ group.sections.forEach((section) => {
616
+ section.settings.forEach((setting) => {
617
+ if (setting.requiresPageRefresh) {
618
+ refreshByKey[setting.name] = true;
619
+ }
620
+ });
621
+ });
622
+ collect(group.groups);
623
+ });
624
+ };
625
+ if (!this.cache) {
626
+ await this.load();
627
+ }
628
+ collect(this.cache);
629
+ return refreshByKey;
630
+ }
738
631
  findGroup(scope, groupName) {
739
632
  return this.searchRecursive(this.getList(scope), groupName, []); // Initialize with an empty breadcrumb
740
633
  }
@@ -1425,6 +1318,8 @@ var AXPCommonSettings;
1425
1318
  AXPCommonSettings["DebugMode"] = "PlatformDevTools:Setting:Developer.DebugMode";
1426
1319
  AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
1427
1320
  AXPCommonSettings["EntityListPersistenceMode"] = "Common:Setting:Entity.ListPersistenceMode";
1321
+ AXPCommonSettings["NavigationMode"] = "Common:Setting:Navigation.Mode";
1322
+ AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
1428
1323
  })(AXPCommonSettings || (AXPCommonSettings = {}));
1429
1324
 
1430
1325
  //#endregion
@@ -1611,9 +1506,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1611
1506
  }]
1612
1507
  }], ctorParameters: () => [] });
1613
1508
 
1509
+ //#endregion
1510
+ /**
1511
+ * Holds detached Angular route component instances keyed by tab key so tabular navigation can
1512
+ * preserve a page's full state (data-table selection, scroll, unsaved form values, etc.) while it
1513
+ * is in the background, then reattach it without re-rendering.
1514
+ *
1515
+ * The registry has no `Router` dependency on purpose: it is consumed by the {@link RouteReuseStrategy}
1516
+ * implementation, so depending on `Router` here would create a construction-time cycle.
1517
+ */
1518
+ class AXPTabRouteCacheRegistry {
1519
+ constructor() {
1520
+ //#region ---- State ----
1521
+ this.handles = new Map();
1522
+ this.openKeys = new Set();
1523
+ this.enabled = false;
1524
+ this.deactivateGuardSuspended = false;
1525
+ }
1526
+ //#endregion
1527
+ //#region ---- Deactivate Guard Suspension ----
1528
+ /**
1529
+ * Runs `action` while unsaved-changes `canDeactivate` guards are suspended, so switching between
1530
+ * tabs preserves a dirty page without prompting. Closing a tab does not use this path, so the
1531
+ * prompt still appears there.
1532
+ */
1533
+ async runWithSuspendedDeactivateGuards(action) {
1534
+ const previous = this.deactivateGuardSuspended;
1535
+ this.deactivateGuardSuspended = true;
1536
+ try {
1537
+ return await action();
1538
+ }
1539
+ finally {
1540
+ this.deactivateGuardSuspended = previous;
1541
+ }
1542
+ }
1543
+ isDeactivateGuardSuspended() {
1544
+ return this.deactivateGuardSuspended;
1545
+ }
1546
+ //#endregion
1547
+ //#region ---- Mode ----
1548
+ /** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
1549
+ setEnabled(enabled) {
1550
+ if (this.enabled === enabled) {
1551
+ return;
1552
+ }
1553
+ this.enabled = enabled;
1554
+ if (!enabled) {
1555
+ this.clear();
1556
+ }
1557
+ }
1558
+ isEnabled() {
1559
+ return this.enabled;
1560
+ }
1561
+ //#endregion
1562
+ //#region ---- Open Keys Sync ----
1563
+ /**
1564
+ * Reconciles open scoped tab keys and active tab URLs. Cached handles whose key is not an open
1565
+ * scoped tab key, an active tab URL, or a child path under an open scoped tab are destroyed.
1566
+ */
1567
+ syncOpenKeys(scopedKeys, activeTabUrls = []) {
1568
+ const scopedSet = new Set(scopedKeys);
1569
+ const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
1570
+ for (const cacheKey of [...this.handles.keys()]) {
1571
+ const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
1572
+ const keepsScopedKey = scopedSet.has(normalizedCacheKey);
1573
+ const keepsActiveUrl = activePathSet.has(normalizedCacheKey);
1574
+ const keepsUnderScopedTab = [...scopedSet].some((scopedKey) => normalizedCacheKey === scopedKey || normalizedCacheKey.startsWith(`${scopedKey}/`));
1575
+ if (!keepsScopedKey && !keepsActiveUrl && !keepsUnderScopedTab) {
1576
+ this.destroyHandle(this.handles.get(cacheKey));
1577
+ this.handles.delete(cacheKey);
1578
+ }
1579
+ }
1580
+ this.openKeys = scopedSet;
1581
+ }
1582
+ isOpen(key) {
1583
+ return this.openKeys.has(key);
1584
+ }
1585
+ /**
1586
+ * Returns the live component instance for a cached background tab, when available.
1587
+ * Matches the tab key directly or any cached handle under a scoped tab prefix.
1588
+ */
1589
+ getComponentInstance(key) {
1590
+ const normalizedKey = key.replace(/\/+$/, '') || '/';
1591
+ const direct = this.handles.get(normalizedKey) ?? this.handles.get(key);
1592
+ if (direct) {
1593
+ return this.readComponentInstance(direct);
1594
+ }
1595
+ for (const [cacheKey, handle] of this.handles) {
1596
+ const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
1597
+ if (normalizedCacheKey === normalizedKey || normalizedCacheKey.startsWith(`${normalizedKey}/`)) {
1598
+ return this.readComponentInstance(handle);
1599
+ }
1600
+ }
1601
+ return null;
1602
+ }
1603
+ //#endregion
1604
+ //#region ---- Handle Store ----
1605
+ /**
1606
+ * Caches a detached background handle. Overwrites without destroying the previous handle: on a
1607
+ * repeat visit Angular hands back a new handle object wrapping the same live `componentRef`, so
1608
+ * destroying the old one would destroy the live component and corrupt the view (NG0922).
1609
+ */
1610
+ store(key, handle) {
1611
+ this.handles.set(key, handle);
1612
+ }
1613
+ /**
1614
+ * Returns the cached handle without mutating the map. Angular reads a handle twice per
1615
+ * navigation (once while building the future state, once while attaching), so `retrieve` must be
1616
+ * idempotent; the handle is released afterwards via {@link release} when Angular calls
1617
+ * `store(snapshot, null)`.
1618
+ */
1619
+ retrieve(key) {
1620
+ return this.handles.get(key) ?? null;
1621
+ }
1622
+ /**
1623
+ * Drops a handle that the router has just reattached (it is now a live foreground view, so it
1624
+ * must not be destroyed). The map keeps only truly backgrounded handles.
1625
+ */
1626
+ release(key) {
1627
+ this.handles.delete(key);
1628
+ }
1629
+ has(key) {
1630
+ return this.handles.has(key);
1631
+ }
1632
+ /** Destroys and removes a single cached handle (used when a tab closes). */
1633
+ evict(key) {
1634
+ this.destroyHandle(this.handles.get(key));
1635
+ this.handles.delete(key);
1636
+ }
1637
+ /** Destroys and removes every cached handle. */
1638
+ clear() {
1639
+ for (const handle of this.handles.values()) {
1640
+ this.destroyHandle(handle);
1641
+ }
1642
+ this.handles.clear();
1643
+ this.openKeys.clear();
1644
+ }
1645
+ //#endregion
1646
+ //#region ---- Utility Methods ----
1647
+ readComponentInstance(handle) {
1648
+ const componentRef = handle.componentRef;
1649
+ return componentRef?.instance ?? null;
1650
+ }
1651
+ destroyHandle(handle) {
1652
+ if (!handle) {
1653
+ return;
1654
+ }
1655
+ const componentRef = handle.componentRef;
1656
+ componentRef?.destroy?.();
1657
+ }
1658
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1659
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, providedIn: 'root' }); }
1660
+ }
1661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, decorators: [{
1662
+ type: Injectable,
1663
+ args: [{
1664
+ providedIn: 'root',
1665
+ }]
1666
+ }] });
1667
+
1614
1668
  //#region ---- Unsaved Changes Can Deactivate Guard Factory ----
1615
1669
  function createUnsavedChangesCanDeactivateGuard() {
1616
- return (component) => component.canDeactivate();
1670
+ return (component) => {
1671
+ // Tab switches keep the page alive in the background, so skip the prompt; closing a tab does not
1672
+ // suspend guards and still prompts.
1673
+ const tabCache = inject(AXPTabRouteCacheRegistry, { optional: true });
1674
+ if (tabCache?.isDeactivateGuardSuspended()) {
1675
+ return true;
1676
+ }
1677
+ return component.canDeactivate();
1678
+ };
1617
1679
  }
1618
1680
  //#endregion
1619
1681
 
@@ -2991,6 +3053,56 @@ function axpRedirectToUnauthorized(router = inject(Router)) {
2991
3053
  return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
2992
3054
  }
2993
3055
 
3056
+ //#endregion
3057
+ /**
3058
+ * Platform formatter for display text: resolves ML objects, i18n keys (`@scope:key`), and primitives.
3059
+ * Register via {@link AXPCommonModule} (auto) or `AXFormatModule.forRoot({ formatters: [AXPTranslateFormatter] })`.
3060
+ */
3061
+ class AXPTranslateFormatter {
3062
+ constructor() {
3063
+ //#region ---- Services & Dependencies ----
3064
+ this.translationService = inject(AXTranslationService);
3065
+ }
3066
+ //#endregion
3067
+ //#region ---- AXFormatter ----
3068
+ get name() {
3069
+ return 'translate';
3070
+ }
3071
+ format(value, options) {
3072
+ if (isNil(value) || value === '') {
3073
+ return '';
3074
+ }
3075
+ if (isObject(value) && !Array.isArray(value) && !(value instanceof Date)) {
3076
+ const resolved = this.translationService.resolve(value);
3077
+ return resolved?.trim() ?? '';
3078
+ }
3079
+ if (isString(value)) {
3080
+ const text = value.trim();
3081
+ if (!text) {
3082
+ return '';
3083
+ }
3084
+ if (text.startsWith('@')) {
3085
+ const translateOptions = typeof options === 'object' ? options : undefined;
3086
+ const translated = translateSync(text, translateOptions);
3087
+ return translated?.trim() ?? text;
3088
+ }
3089
+ return text;
3090
+ }
3091
+ if (typeof value === 'number' || typeof value === 'boolean') {
3092
+ return String(value);
3093
+ }
3094
+ if (value instanceof Date) {
3095
+ return value.toISOString();
3096
+ }
3097
+ return String(value).trim();
3098
+ }
3099
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3100
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter }); }
3101
+ }
3102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, decorators: [{
3103
+ type: Injectable
3104
+ }] });
3105
+
2994
3106
  class AXPFileStorageService {
2995
3107
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2996
3108
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService }); }
@@ -4756,6 +4868,8 @@ class AXPMenuSearchDefinitionProvider {
4756
4868
  }
4757
4869
  }
4758
4870
 
4871
+ const AXP_SEARCH_DEFINITION_LAZY_RESOLVER = new InjectionToken('AXP_SEARCH_DEFINITION_LAZY_RESOLVER');
4872
+
4759
4873
  class AXPSearchDefinitionProviderContext {
4760
4874
  constructor() {
4761
4875
  this.definitions = [];
@@ -4830,6 +4944,7 @@ const AXP_SEARCH_DEFINITION_PROVIDER = new InjectionToken('AXP_SEARCH_DEFINITION
4830
4944
  class AXPSearchDefinitionProviderService {
4831
4945
  constructor() {
4832
4946
  this.providers = inject(AXP_SEARCH_DEFINITION_PROVIDER, { optional: true });
4947
+ this.lazyResolver = inject(AXP_SEARCH_DEFINITION_LAZY_RESOLVER, { optional: true });
4833
4948
  this.cache = null;
4834
4949
  }
4835
4950
  async load() {
@@ -4860,12 +4975,29 @@ class AXPSearchDefinitionProviderService {
4860
4975
  }
4861
4976
  }
4862
4977
  findDefinition(definitionName) {
4863
- const definition = this.getList().find((definition) => definition.name === definitionName);
4978
+ const definition = this.getList().find((item) => item.name === definitionName);
4864
4979
  if (!definition) {
4865
4980
  throw new Error(`Definition with name ${definitionName} not found`);
4866
4981
  }
4867
4982
  return definition;
4868
4983
  }
4984
+ async findDefinitionAsync(definitionName) {
4985
+ await this.load();
4986
+ const existing = this.getList().find((item) => item.name === definitionName);
4987
+ if (existing) {
4988
+ return existing;
4989
+ }
4990
+ const lazy = this.lazyResolver;
4991
+ if (!lazy) {
4992
+ return null;
4993
+ }
4994
+ const resolved = await lazy.resolve(definitionName);
4995
+ if (!resolved) {
4996
+ return null;
4997
+ }
4998
+ this.cache = [...(this.cache ?? []), resolved];
4999
+ return resolved;
5000
+ }
4869
5001
  findDefinitionByGroup(definitionGroup) {
4870
5002
  const definition = this.getList().find((definition) => definition.group === definitionGroup);
4871
5003
  if (!definition) {
@@ -4884,17 +5016,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4884
5016
  //#region ---- Imports ----
4885
5017
  //#endregion
4886
5018
  //#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
- }
5019
+ /** @see normalizeExpressionTemplate */
5020
+ const normalizeEntityDisplayTemplate = normalizeExpressionTemplate;
4898
5021
  /** True when formatted text still contains unresolved template markers. */
4899
5022
  function isUnresolvedEntityDisplayTemplate(value) {
4900
5023
  return /\{\{/.test(value) || /context\.eval\s*\(/.test(value) || value === '[object Object]';
@@ -5025,17 +5148,55 @@ function resolveEntityRowTitleTemplate(entity) {
5025
5148
  }
5026
5149
  return undefined;
5027
5150
  }
5151
+ /**
5152
+ * Record title template for modify popups: `interfaces.master.update.title` → `single.title`.
5153
+ */
5154
+ function resolveEntityModifyRecordTitleTemplate(entity) {
5155
+ if (!entity) {
5156
+ return undefined;
5157
+ }
5158
+ const updateTitle = entity.interfaces?.master?.update?.title?.trim();
5159
+ if (updateTitle && isRowBoundTemplate(updateTitle)) {
5160
+ return normalizeEntityDisplayTemplate(updateTitle);
5161
+ }
5162
+ const singleTitle = entity.interfaces?.master?.single?.title?.trim();
5163
+ if (singleTitle && isRowBoundTemplate(singleTitle)) {
5164
+ return normalizeEntityDisplayTemplate(singleTitle);
5165
+ }
5166
+ return undefined;
5167
+ }
5168
+ /**
5169
+ * Modify/edit popup chrome: `{action} {individual} ({update.title ?? single.title})` when a record template exists.
5170
+ */
5171
+ async function resolveEntityModifyPopupTitle(entity, recordData, translationService, formatService, expressionEvaluator, contextEvalFactory, actionTitleKey = '@general:actions.modify.title') {
5172
+ const actionPrefix = translationService.translateSync(actionTitleKey);
5173
+ const entityLabel = translationService.translateSync(entity.formats.individual);
5174
+ const baseTitle = `${actionPrefix} ${entityLabel}`.trim();
5175
+ const recordTemplate = resolveEntityModifyRecordTitleTemplate(entity);
5176
+ if (!recordTemplate) {
5177
+ return baseTitle;
5178
+ }
5179
+ const locale = translationService.getActiveLang();
5180
+ const resolved = await resolveSearchDisplayTemplate(recordTemplate, recordData, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
5181
+ const recordTitle = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
5182
+ ? resolved
5183
+ : resolveSearchableText(resolved, locale, translationService));
5184
+ if (!isMeaningfulEntityDisplayTitle(recordTitle)) {
5185
+ return baseTitle;
5186
+ }
5187
+ return `${baseTitle} (${recordTitle})`;
5188
+ }
5028
5189
  //#endregion
5029
5190
  //#region ---- Runtime resolution ----
5030
5191
  /**
5031
5192
  * Resolves an entity row title using entity-driven fallback templates and property paths.
5032
5193
  */
5033
- async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator) {
5194
+ async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
5034
5195
  if (data == null) {
5035
5196
  return '';
5036
5197
  }
5037
5198
  for (const template of format.titleFallbackTemplates ?? []) {
5038
- const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator);
5199
+ const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
5039
5200
  const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
5040
5201
  ? resolved
5041
5202
  : resolveSearchableText(resolved, locale, translationService));
@@ -5076,13 +5237,8 @@ function resolveSearchableText(value, locale, translationService) {
5076
5237
  }
5077
5238
  return trimmed;
5078
5239
  }
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);
5240
+ if (isLocaleStringMap(value) || translationService.isValidMultiLanguageObject(value)) {
5241
+ return resolveMultiLanguageString(value, locale).trim();
5086
5242
  }
5087
5243
  return '';
5088
5244
  }
@@ -5094,6 +5250,11 @@ function searchInSearchableValue(value, query, locale, translationService) {
5094
5250
  if (!q) {
5095
5251
  return true;
5096
5252
  }
5253
+ if (isLocaleStringMap(value)) {
5254
+ return Object.values(value)
5255
+ .filter((entry) => typeof entry === 'string' && entry.trim() !== '')
5256
+ .some((entry) => entry.toLocaleLowerCase(locale).includes(q));
5257
+ }
5097
5258
  const text = resolveSearchableText(value, locale, translationService).toLocaleLowerCase(locale);
5098
5259
  return text.includes(q);
5099
5260
  }
@@ -5126,26 +5287,31 @@ function searchInRecordValues(value, query, locale, translationService, depth =
5126
5287
  function countMustacheExpressions(template) {
5127
5288
  return (template.match(/\{\{/g) ?? []).length;
5128
5289
  }
5129
- /** Standard row scope for entity title/search templates. */
5130
- function createEntityRowExpressionScope(data) {
5290
+ /** Standard display-tier row scope for entity title/search templates. */
5291
+ function createEntityRowExpressionScope(data, contextEvalFactory) {
5131
5292
  return {
5132
- context: {
5133
- eval: (path) => get(data, path),
5134
- },
5293
+ context: contextEvalFactory.createScope((data ?? {})),
5135
5294
  };
5136
5295
  }
5296
+ /**
5297
+ * Coerces an evaluated display segment to plain text.
5298
+ * Piped eval returns strings; unpiped MLS still resolves via compat fallback.
5299
+ */
5300
+ function formatDisplaySegment(segment, locale, translationService) {
5301
+ if (segment === false || segment == null) {
5302
+ return '';
5303
+ }
5304
+ if (isString(segment)) {
5305
+ return segment.trim();
5306
+ }
5307
+ return resolveSearchableText(segment, locale, translationService);
5308
+ }
5137
5309
  /**
5138
5310
  * Evaluates a normalized entity display template via {@link AXPExpressionEvaluatorService}.
5139
5311
  */
5140
- async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService) {
5312
+ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory) {
5141
5313
  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
- };
5314
+ const scope = createEntityRowExpressionScope(data, contextEvalFactory);
5149
5315
  if (!normalized.includes('{{')) {
5150
5316
  return normalized;
5151
5317
  }
@@ -5159,7 +5325,7 @@ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator
5159
5325
  for (const match of normalized.matchAll(regex)) {
5160
5326
  result += normalized.slice(lastIndex, match.index);
5161
5327
  const segment = await expressionEvaluator.evaluate(`{{ ${match[1].trim()} }}`, scope);
5162
- result += formatSegment(segment);
5328
+ result += formatDisplaySegment(segment, locale, translationService);
5163
5329
  lastIndex = (match.index ?? 0) + match[0].length;
5164
5330
  }
5165
5331
  result += normalized.slice(lastIndex);
@@ -5206,7 +5372,7 @@ function resolveQueryMatchingDisplayText(value, query, locale, translationServic
5206
5372
  /**
5207
5373
  * Resolves entity search display templates; coerces multi-language field values to readable text.
5208
5374
  */
5209
- async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator) {
5375
+ async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
5210
5376
  if (!template) {
5211
5377
  return '';
5212
5378
  }
@@ -5217,7 +5383,7 @@ async function resolveSearchDisplayTemplate(template, data, locale, translationS
5217
5383
  const formatted = formatService.format(template, 'string', data);
5218
5384
  return sanitizeResolvedEntityDisplayTitle(typeof formatted === 'string' ? formatted : String(formatted ?? ''));
5219
5385
  }
5220
- const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService);
5386
+ const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory);
5221
5387
  return sanitizeResolvedEntityDisplayTitle(resolveSearchableText(evaluated, locale, translationService));
5222
5388
  }
5223
5389
  //#endregion
@@ -5242,6 +5408,7 @@ class AXPSearchService {
5242
5408
  this.formatService = inject(AXFormatService);
5243
5409
  this.translationService = inject(AXTranslationService);
5244
5410
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
5411
+ this.contextEvalFactory = inject(AXPContextEvalFactory);
5245
5412
  }
5246
5413
  async search(text) {
5247
5414
  //TODO better handle this
@@ -5271,20 +5438,23 @@ class AXPSearchService {
5271
5438
  for (const resultArray of results) {
5272
5439
  for (const result of resultArray) {
5273
5440
  try {
5274
- const definition = this.definitionService.findDefinition(result.group);
5441
+ const definition = await this.definitionService.findDefinitionAsync(result.group);
5442
+ if (!definition) {
5443
+ continue;
5444
+ }
5275
5445
  const resolvedTitle = result.title ??
5276
5446
  (definition.format.title
5277
- ? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
5447
+ ? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
5278
5448
  : '');
5279
5449
  const resolvedDescription = result.description ??
5280
5450
  (definition.format.description
5281
- ? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
5451
+ ? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
5282
5452
  : undefined);
5283
5453
  let titleText = sanitizeResolvedEntityDisplayTitle(typeof resolvedTitle === 'string'
5284
5454
  ? resolvedTitle
5285
5455
  : resolveSearchableText(resolvedTitle, locale, this.translationService));
5286
5456
  if (!isMeaningfulEntityDisplayTitle(titleText)) {
5287
- titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator);
5457
+ titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory);
5288
5458
  }
5289
5459
  if (!isMeaningfulEntityDisplayTitle(titleText)) {
5290
5460
  continue;
@@ -5779,62 +5949,1116 @@ function axpReplaceEntityListRouteQueryParams(router, queryParams) {
5779
5949
  }
5780
5950
  //#endregion
5781
5951
 
5782
- //TODO Loading, Redirect, Drawer, Show toast
5783
- const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
5784
- const AXPRefreshEvent = createWorkFlowEvent('Refresh Event Fired');
5785
- const AXPReloadEvent = createWorkFlowEvent('Reload Event Fired');
5786
- class AXPWorkflowNavigateAction extends AXPWorkflowAction {
5787
- constructor() {
5788
- super(...arguments);
5789
- this.router = inject(Router);
5952
+ //#region ---- Tab Key ----
5953
+ /** Query params ignored when building a tab dedupe key. */
5954
+ const IGNORED_QUERY_KEYS = new Set(['_ts']);
5955
+ /**
5956
+ * Builds a stable tab key from a router URL.
5957
+ * Path segments define identity; query params are excluded so the same entity record
5958
+ * or list reuses one tab even when filters or refresh tokens change.
5959
+ */
5960
+ function axpBuildTabKey(url) {
5961
+ const normalized = normalizeRouterUrl(url);
5962
+ const pathOnly = normalized.split('?')[0].split('#')[0];
5963
+ return pathOnly;
5964
+ }
5965
+ /** Normalizes a router URL for storage and navigation. */
5966
+ function normalizeRouterUrl(url) {
5967
+ const trimmed = url.trim();
5968
+ if (!trimmed.length) {
5969
+ return '/';
5970
+ }
5971
+ const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
5972
+ const [pathAndFragment, queryString = ''] = withLeadingSlash.split('?');
5973
+ const [pathPart, fragment = ''] = pathAndFragment.split('#');
5974
+ const normalizedPath = pathPart.replace(/\/+$/, '') || '/';
5975
+ const filteredQuery = filterQueryString(queryString);
5976
+ let result = normalizedPath;
5977
+ if (filteredQuery) {
5978
+ result += `?${filteredQuery}`;
5979
+ }
5980
+ if (fragment) {
5981
+ result += `#${fragment}`;
5790
5982
  }
5791
- async execute(context) {
5792
- const payload = context.getVariable('payload');
5793
- if (Array.isArray(payload.commands)) {
5794
- await this.router.navigate(payload.commands, payload.extras);
5795
- return;
5796
- }
5797
- const path = payload.commands;
5798
- if (!path) {
5799
- return;
5800
- }
5801
- if (path.toLowerCase().startsWith('http') || payload.target === 'blank') {
5802
- window.open(path, '_blank');
5803
- return;
5804
- }
5805
- await axpNavigateAppPath(this.router, path, payload.extras);
5983
+ return result;
5984
+ }
5985
+ function filterQueryString(queryString) {
5986
+ if (!queryString) {
5987
+ return '';
5806
5988
  }
5807
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
5808
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
5989
+ const params = new URLSearchParams(queryString);
5990
+ const kept = [];
5991
+ params.forEach((value, key) => {
5992
+ if (!IGNORED_QUERY_KEYS.has(key)) {
5993
+ kept.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
5994
+ }
5995
+ });
5996
+ kept.sort();
5997
+ return kept.join('&');
5809
5998
  }
5810
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, decorators: [{
5811
- type: Injectable
5812
- }] });
5813
- class AXPDialogConfirmAction extends AXPWorkflowAction {
5999
+ /** Human-readable fallback title from the URL path. */
6000
+ function axpFallbackTabTitle(url) {
6001
+ const pathOnly = url.split('?')[0].split('#')[0];
6002
+ const segments = pathOnly.split('/').filter(Boolean);
6003
+ const last = defaultTo(segments[segments.length - 1], 'Page');
6004
+ return decodeURIComponent(last);
6005
+ }
6006
+ //#endregion
6007
+
6008
+ //#region ---- Types ----
6009
+ const AXP_HOME_TAB_KEY = 'axp:home';
6010
+ /** Route `data` key for tab-navigation behavior (`data.tab`). */
6011
+ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
6012
+ //#endregion
6013
+
6014
+ //#region ---- Tab Preview Service ----
6015
+ /**
6016
+ * In-memory structured previews for tab hover cards and dirty indicators.
6017
+ * Populated by active/cached pages via {@link AXPPageLayoutBaseComponent}.
6018
+ */
6019
+ class AXPTabPreviewService {
5814
6020
  constructor() {
5815
- super(...arguments);
5816
- this.dialogService = inject(AXDialogService);
5817
- this.translationService = inject(AXTranslationService);
5818
- this.defaultAction = 'cancel';
6021
+ //#region ---- State ----
6022
+ this.previewMap = signal({}, ...(ngDevMode ? [{ debugName: "previewMap" }] : /* istanbul ignore next */ []));
6023
+ this.previews = this.previewMap.asReadonly();
5819
6024
  }
5820
- async execute(context) {
5821
- console.log('AXPDialogConfirmAction', this.title, this.message, this.type);
5822
- context.setOutput('result', false);
5823
- const dialogResult = await this.dialogService.confirm(await this.translationService.translateAsync(this.title), await this.translationService.translateAsync(this.message), (await this.translationService.translateAsync(this.type)), 'horizontal', false, 'cancel');
5824
- context.setOutput('result', dialogResult.result);
6025
+ //#endregion
6026
+ //#region ---- Public API ----
6027
+ setPreview(key, preview) {
6028
+ this.previewMap.update((current) => ({
6029
+ ...current,
6030
+ [key]: preview,
6031
+ }));
6032
+ }
6033
+ patchPreview(key, patch) {
6034
+ this.previewMap.update((current) => {
6035
+ const existing = current[key];
6036
+ if (!existing) {
6037
+ if (!patch.url) {
6038
+ return current;
6039
+ }
6040
+ return {
6041
+ ...current,
6042
+ [key]: {
6043
+ title: patch.title ?? '',
6044
+ url: patch.url,
6045
+ icon: patch.icon,
6046
+ subtitle: patch.subtitle,
6047
+ isDirty: patch.isDirty ?? false,
6048
+ breadcrumbTrail: patch.breadcrumbTrail ?? [],
6049
+ },
6050
+ };
6051
+ }
6052
+ return {
6053
+ ...current,
6054
+ [key]: {
6055
+ ...existing,
6056
+ ...patch,
6057
+ },
6058
+ };
6059
+ });
5825
6060
  }
5826
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
5827
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction }); }
5828
- }
5829
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, decorators: [{
5830
- type: Injectable
5831
- }] });
5832
- class AXPToastAction extends AXPWorkflowAction {
5833
- constructor() {
5834
- super(...arguments);
5835
- this.toastService = inject(AXToastService);
5836
- this.translationService = inject(AXTranslationService);
5837
- this.settingsService = inject(AXPSettingsService);
6061
+ getPreview(key) {
6062
+ return this.previewMap()[key];
6063
+ }
6064
+ isTabDirty(key) {
6065
+ return !!this.previewMap()[key]?.isDirty;
6066
+ }
6067
+ clearPreview(key) {
6068
+ this.previewMap.update((current) => {
6069
+ if (!(key in current)) {
6070
+ return current;
6071
+ }
6072
+ const next = { ...current };
6073
+ delete next[key];
6074
+ return next;
6075
+ });
6076
+ }
6077
+ clearPreviews(keys) {
6078
+ const keySet = new Set(keys);
6079
+ if (!keySet.size) {
6080
+ return;
6081
+ }
6082
+ this.previewMap.update((current) => {
6083
+ const next = { ...current };
6084
+ let changed = false;
6085
+ for (const key of keySet) {
6086
+ if (key in next) {
6087
+ delete next[key];
6088
+ changed = true;
6089
+ }
6090
+ }
6091
+ return changed ? next : current;
6092
+ });
6093
+ }
6094
+ clearAll() {
6095
+ this.previewMap.set({});
6096
+ }
6097
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6098
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, providedIn: 'root' }); }
6099
+ }
6100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, decorators: [{
6101
+ type: Injectable,
6102
+ args: [{
6103
+ providedIn: 'root',
6104
+ }]
6105
+ }] });
6106
+
6107
+ //#region ---- Tab Route Policy ----
6108
+ const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
6109
+ const EXCLUDED_PATH_PATTERNS = [
6110
+ /^\/assessment\/fill\//i,
6111
+ /^\/forms\//i,
6112
+ ];
6113
+ /**
6114
+ * Returns true when the URL represents an in-app page that may appear in the tab bar.
6115
+ */
6116
+ function axpIsTabEligibleUrl(url) {
6117
+ const pathOnly = url.split('?')[0].split('#')[0].trim();
6118
+ if (!pathOnly || pathOnly === '/') {
6119
+ return false;
6120
+ }
6121
+ const normalized = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
6122
+ if (EXCLUDED_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
6123
+ return false;
6124
+ }
6125
+ if (EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(normalized))) {
6126
+ return false;
6127
+ }
6128
+ return true;
6129
+ }
6130
+ //#endregion
6131
+
6132
+ //#region ---- Route Data ----
6133
+ /** True when the route declares itself as a scoped tab root (`data.tab = true`). */
6134
+ function axpHasTabScope(data) {
6135
+ return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
6136
+ }
6137
+ //#endregion
6138
+ //#region ---- Snapshot Resolution ----
6139
+ /** Resolved URL path for a snapshot (no query or fragment). */
6140
+ function axpPathFromSnapshot(snapshot) {
6141
+ const segments = snapshot.pathFromRoot
6142
+ .flatMap((entry) => entry.url.map((segment) => segment.path))
6143
+ .filter(Boolean);
6144
+ return segments.length ? `/${segments.join('/')}` : '/';
6145
+ }
6146
+ /** Returns the leaf snapshot under the router state root. */
6147
+ function axpGetLeafRouteSnapshot(root) {
6148
+ let route = root;
6149
+ while (route.firstChild) {
6150
+ route = route.firstChild;
6151
+ }
6152
+ return route;
6153
+ }
6154
+ /**
6155
+ * Resolves the tab dedupe key from an activated route tree.
6156
+ * Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
6157
+ */
6158
+ function axpResolveTabKeyFromSnapshot(leaf) {
6159
+ const chain = leaf.pathFromRoot;
6160
+ for (let index = chain.length - 1; index >= 0; index--) {
6161
+ const snapshot = chain[index];
6162
+ if (axpHasTabScope(snapshot.data)) {
6163
+ return axpPathFromSnapshot(snapshot);
6164
+ }
6165
+ }
6166
+ return axpPathFromSnapshot(leaf);
6167
+ }
6168
+ //#endregion
6169
+ //#region ---- Router / URL Resolution ----
6170
+ /**
6171
+ * Resolves a tab key for the given URL (or the current router URL).
6172
+ * Prefers the live route snapshot when it matches the target URL; otherwise matches route config.
6173
+ */
6174
+ function axpResolveTabKey(router, url) {
6175
+ const normalized = normalizeRouterUrl(url ?? router.url);
6176
+ const pathOnly = normalized.split('?')[0].split('#')[0];
6177
+ const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
6178
+ if (!url || pathOnly === currentPath) {
6179
+ const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
6180
+ return axpResolveTabKeyFromSnapshot(leaf);
6181
+ }
6182
+ const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
6183
+ if (fromConfig) {
6184
+ return fromConfig;
6185
+ }
6186
+ return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
6187
+ }
6188
+ /**
6189
+ * Matches a URL path against registered routes and resolves the tab key from `data.tab`.
6190
+ */
6191
+ function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
6192
+ const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
6193
+ const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
6194
+ const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
6195
+ if (!chain?.length) {
6196
+ return null;
6197
+ }
6198
+ for (let index = chain.length - 1; index >= 0; index--) {
6199
+ const level = chain[index];
6200
+ if (axpHasTabScope(level.mergedData)) {
6201
+ return level.path;
6202
+ }
6203
+ }
6204
+ return chain[chain.length - 1]?.path ?? null;
6205
+ }
6206
+ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
6207
+ let bestMatch = null;
6208
+ for (const route of routes) {
6209
+ if (route.redirectTo) {
6210
+ continue;
6211
+ }
6212
+ const segmentMatch = matchRoutePath(route.path ?? '', segments, startIndex);
6213
+ if (!segmentMatch) {
6214
+ continue;
6215
+ }
6216
+ const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
6217
+ const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
6218
+ const mergedData = { ...inheritedData, ...(route.data ?? {}) };
6219
+ const level = { mergedData, path };
6220
+ const nextChain = [...chain, level];
6221
+ const nextIndex = startIndex + segmentMatch.consumed;
6222
+ if (route.children?.length) {
6223
+ const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
6224
+ if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
6225
+ bestMatch = childMatch;
6226
+ }
6227
+ }
6228
+ if (nextIndex === segments.length) {
6229
+ if (!bestMatch || nextChain.length > bestMatch.length) {
6230
+ bestMatch = nextChain;
6231
+ }
6232
+ }
6233
+ }
6234
+ return bestMatch;
6235
+ }
6236
+ function matchRoutePath(routePath, segments, startIndex) {
6237
+ if (routePath === '**') {
6238
+ return { consumed: Math.max(0, segments.length - startIndex) };
6239
+ }
6240
+ const parts = routePath.split('/').filter(Boolean);
6241
+ if (!parts.length) {
6242
+ return { consumed: 0 };
6243
+ }
6244
+ let consumed = 0;
6245
+ for (const part of parts) {
6246
+ const segment = segments[startIndex + consumed];
6247
+ if (segment === undefined) {
6248
+ return null;
6249
+ }
6250
+ if (part.startsWith(':') || part === segment) {
6251
+ consumed++;
6252
+ continue;
6253
+ }
6254
+ return null;
6255
+ }
6256
+ return { consumed };
6257
+ }
6258
+ //#endregion
6259
+
6260
+ //#region ---- Constants ----
6261
+ const SESSION_STORAGE_PREFIX = 'AXP_TAB_NAV';
6262
+ //#endregion
6263
+ class AXPTabNavigationService {
6264
+ //#endregion
6265
+ //#region ---- Lifecycle ----
6266
+ constructor() {
6267
+ //#region ---- Services & Dependencies ----
6268
+ this.router = inject(Router);
6269
+ this.settingsService = inject(AXPSettingsService);
6270
+ this.deviceService = inject(AXPDeviceService);
6271
+ this.menuService = inject(AXPMenuService);
6272
+ this.homePageService = inject(AXPHomePageService);
6273
+ this.sessionService = inject(AXPSessionService);
6274
+ this.routeCacheRegistry = inject(AXPTabRouteCacheRegistry);
6275
+ this.tabPreviewService = inject(AXPTabPreviewService);
6276
+ this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
6277
+ this.destroyRef = inject(DestroyRef);
6278
+ //#endregion
6279
+ //#region ---- State ----
6280
+ this.navigationMode = signal('simple', ...(ngDevMode ? [{ debugName: "navigationMode" }] : /* istanbul ignore next */ []));
6281
+ this.maxOpenTabs = signal(5, ...(ngDevMode ? [{ debugName: "maxOpenTabs" }] : /* istanbul ignore next */ []));
6282
+ this.tabs = signal([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
6283
+ this.activeTabKey = signal(null, ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
6284
+ this.suppressNavigationSync = false;
6285
+ //#endregion
6286
+ //#region ---- Computed ----
6287
+ /** Effective mode: mobile always uses simple navigation. */
6288
+ this.effectiveMode = computed(() => {
6289
+ if (this.deviceService.isSmall()) {
6290
+ return 'simple';
6291
+ }
6292
+ return this.navigationMode();
6293
+ }, ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
6294
+ this.isTabBarVisible = computed(() => this.effectiveMode() === 'tabular' && this.tabs().length > 0, ...(ngDevMode ? [{ debugName: "isTabBarVisible" }] : /* istanbul ignore next */ []));
6295
+ this.tabList = this.tabs.asReadonly();
6296
+ this.activeKey = this.activeTabKey.asReadonly();
6297
+ void this.loadSettings();
6298
+ this.bindRouterEvents();
6299
+ this.bindSettingsChanges();
6300
+ this.bindSessionChanges();
6301
+ this.bindRouteCacheSync();
6302
+ }
6303
+ //#endregion
6304
+ //#region ---- Public API ----
6305
+ /** Navigates to a path, opening or activating a tab when tabular mode is active. */
6306
+ async navigate(path, extras) {
6307
+ const normalizedPath = normalizeRouterUrl(path);
6308
+ if (this.effectiveMode() === 'simple' || !axpIsTabEligibleUrl(normalizedPath)) {
6309
+ return axpNavigateAppPath(this.router, normalizedPath, extras);
6310
+ }
6311
+ await this.openOrActivateTab(normalizedPath);
6312
+ return true;
6313
+ }
6314
+ /** Opens a tab or activates an existing tab with the same key. */
6315
+ async openOrActivateTab(url, title) {
6316
+ if (this.effectiveMode() === 'simple') {
6317
+ await axpNavigateAppPath(this.router, url);
6318
+ return;
6319
+ }
6320
+ const normalizedUrl = normalizeRouterUrl(url);
6321
+ if (!axpIsTabEligibleUrl(normalizedUrl)) {
6322
+ await axpNavigateAppPath(this.router, normalizedUrl);
6323
+ return;
6324
+ }
6325
+ await this.ensureHomeTab();
6326
+ if (this.isHomePageUrl(normalizedUrl)) {
6327
+ await this.activateTabKey(AXP_HOME_TAB_KEY, normalizedUrl);
6328
+ return;
6329
+ }
6330
+ const resolvedKey = axpResolveTabKey(this.router, normalizedUrl);
6331
+ const existing = this.findTabForUrl(normalizedUrl);
6332
+ if (existing) {
6333
+ if (existing.key !== resolvedKey) {
6334
+ this.migrateTabKey(existing.key, resolvedKey);
6335
+ }
6336
+ this.updateTabUrl(resolvedKey, normalizedUrl);
6337
+ await this.activateTabKey(resolvedKey, normalizedUrl);
6338
+ return;
6339
+ }
6340
+ // Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
6341
+ this.suppressNavigationSync = true;
6342
+ try {
6343
+ await axpNavigateAppPath(this.router, normalizedUrl);
6344
+ const key = axpResolveTabKey(this.router);
6345
+ const postNavExisting = this.findTabForUrl(normalizedUrl);
6346
+ if (postNavExisting) {
6347
+ if (postNavExisting.key !== key) {
6348
+ this.migrateTabKey(postNavExisting.key, key);
6349
+ }
6350
+ this.updateTabUrl(key, normalizedUrl);
6351
+ this.touchTab(key);
6352
+ this.activeTabKey.set(key);
6353
+ this.persistSession();
6354
+ return;
6355
+ }
6356
+ this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
6357
+ this.activeTabKey.set(key);
6358
+ this.persistSession();
6359
+ }
6360
+ finally {
6361
+ this.suppressNavigationSync = false;
6362
+ }
6363
+ }
6364
+ /**
6365
+ * Activates a tab and navigates to its URL. The previously active page is detached and cached by
6366
+ * the reuse strategy, so its state is preserved; this tab's cached instance is reattached when
6367
+ * available instead of being re-rendered.
6368
+ *
6369
+ * @param suspendDeactivateGuard When true (default for tab switches), unsaved-changes prompts are
6370
+ * skipped so switching tabs keeps a dirty page alive. Closing a tab passes false to keep the prompt.
6371
+ */
6372
+ async activateTabKey(key, urlOverride, suspendDeactivateGuard = true) {
6373
+ const tab = this.tabs().find((item) => item.key === key);
6374
+ if (!tab) {
6375
+ return;
6376
+ }
6377
+ const targetUrl = urlOverride ?? tab.url;
6378
+ this.touchTab(key);
6379
+ this.activeTabKey.set(key);
6380
+ this.persistSession();
6381
+ const navigate = async () => {
6382
+ this.suppressNavigationSync = true;
6383
+ try {
6384
+ await this.router.navigateByUrl(targetUrl);
6385
+ }
6386
+ finally {
6387
+ this.suppressNavigationSync = false;
6388
+ }
6389
+ };
6390
+ if (suspendDeactivateGuard) {
6391
+ await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
6392
+ return;
6393
+ }
6394
+ await navigate();
6395
+ }
6396
+ /** Closes a tab and navigates to a neighbor when the active tab is closed. */
6397
+ async closeTab(key) {
6398
+ await this.closeTabs([key]);
6399
+ }
6400
+ /** Closes every closable tab except the given key (pinned tabs are kept). */
6401
+ async closeOtherTabs(keepKey) {
6402
+ const keys = this.tabs()
6403
+ .filter((tab) => tab.closable && !tab.pinned && tab.key !== keepKey)
6404
+ .map((tab) => tab.key);
6405
+ await this.closeTabs(keys);
6406
+ }
6407
+ /** Closes all closable tabs (pinned tabs are kept). */
6408
+ async closeAllTabs() {
6409
+ const keys = this.tabs()
6410
+ .filter((tab) => tab.closable && !tab.pinned)
6411
+ .map((tab) => tab.key);
6412
+ await this.closeTabs(keys);
6413
+ }
6414
+ /** Toggles whether a tab stays pinned in the strip (home tab is always pinned). */
6415
+ toggleTabPinned(key) {
6416
+ if (key === AXP_HOME_TAB_KEY) {
6417
+ return;
6418
+ }
6419
+ this.tabs.update((items) => {
6420
+ const updated = items.map((tab) => {
6421
+ if (tab.key !== key) {
6422
+ return tab;
6423
+ }
6424
+ const pinned = !tab.pinned;
6425
+ return {
6426
+ ...tab,
6427
+ pinned,
6428
+ closable: !pinned,
6429
+ };
6430
+ });
6431
+ return this.normalizeTabOrder(updated);
6432
+ });
6433
+ this.persistSession();
6434
+ }
6435
+ /**
6436
+ * Reorders tabs after drag-and-drop. Home stays first; pinned and unpinned tabs cannot cross groups.
6437
+ */
6438
+ reorderTabs(previousIndex, currentIndex) {
6439
+ if (previousIndex === currentIndex) {
6440
+ return;
6441
+ }
6442
+ const items = this.tabs();
6443
+ if (previousIndex < 0 || currentIndex < 0 || previousIndex >= items.length || currentIndex >= items.length) {
6444
+ return;
6445
+ }
6446
+ const moving = items[previousIndex];
6447
+ if (moving.key === AXP_HOME_TAB_KEY) {
6448
+ return;
6449
+ }
6450
+ const lastPinnedIndex = this.getLastPinnedTabIndex(items);
6451
+ let targetIndex = currentIndex;
6452
+ if (moving.pinned) {
6453
+ if (targetIndex === 0) {
6454
+ return;
6455
+ }
6456
+ targetIndex = Math.max(1, Math.min(targetIndex, lastPinnedIndex));
6457
+ }
6458
+ else {
6459
+ const firstUnpinnedIndex = lastPinnedIndex + 1;
6460
+ if (firstUnpinnedIndex >= items.length) {
6461
+ return;
6462
+ }
6463
+ targetIndex = Math.max(firstUnpinnedIndex, Math.min(targetIndex, items.length - 1));
6464
+ }
6465
+ if (previousIndex === targetIndex) {
6466
+ return;
6467
+ }
6468
+ const next = [...items];
6469
+ const [removed] = next.splice(previousIndex, 1);
6470
+ next.splice(targetIndex, 0, removed);
6471
+ this.tabs.set(next);
6472
+ this.persistSession();
6473
+ }
6474
+ /** Updates a tab title from the current page chrome (e.g. breadcrumb trail). */
6475
+ syncTabTitle(key, title) {
6476
+ if (this.effectiveMode() === 'simple') {
6477
+ return;
6478
+ }
6479
+ if (!this.tabs().some((tab) => tab.key === key)) {
6480
+ return;
6481
+ }
6482
+ const normalized = title.trim();
6483
+ if (!normalized || this.isGenericHomeTitle(normalized)) {
6484
+ return;
6485
+ }
6486
+ this.updateTabTitle(key, normalized);
6487
+ }
6488
+ /** Updates a tab icon from the current page chrome or menu metadata. */
6489
+ syncTabIcon(key, icon) {
6490
+ if (this.effectiveMode() === 'simple') {
6491
+ return;
6492
+ }
6493
+ if (!this.tabs().some((tab) => tab.key === key)) {
6494
+ return;
6495
+ }
6496
+ const normalized = icon.trim();
6497
+ if (!normalized) {
6498
+ return;
6499
+ }
6500
+ this.updateTabIcon(key, normalized);
6501
+ }
6502
+ //#endregion
6503
+ //#region ---- Bulk Tab Close ----
6504
+ /** Closes multiple tabs in one update; navigates once when the active tab is closed. */
6505
+ async closeTabs(keys) {
6506
+ const uniqueKeys = [...new Set(keys)];
6507
+ if (!uniqueKeys.length) {
6508
+ return;
6509
+ }
6510
+ const closableKeySet = new Set(uniqueKeys.filter((key) => {
6511
+ const tab = this.tabs().find((item) => item.key === key);
6512
+ return !!tab?.closable && !tab.pinned;
6513
+ }));
6514
+ if (!closableKeySet.size) {
6515
+ return;
6516
+ }
6517
+ const activeKey = this.activeTabKey();
6518
+ const backgroundKeys = [...closableKeySet].filter((key) => key !== activeKey);
6519
+ for (const key of backgroundKeys) {
6520
+ if (!(await this.confirmBackgroundTabClose(key))) {
6521
+ closableKeySet.delete(key);
6522
+ }
6523
+ }
6524
+ if (!closableKeySet.size) {
6525
+ return;
6526
+ }
6527
+ const currentTabs = this.tabs();
6528
+ const wasActiveClosed = !!activeKey && closableKeySet.has(activeKey);
6529
+ const closedActiveIndex = wasActiveClosed ? currentTabs.findIndex((item) => item.key === activeKey) : -1;
6530
+ const nextTabs = currentTabs.filter((item) => !closableKeySet.has(item.key));
6531
+ if (wasActiveClosed) {
6532
+ const navigated = await this.navigateAwayBeforeClosingActiveTab(closedActiveIndex, nextTabs);
6533
+ if (!navigated) {
6534
+ return;
6535
+ }
6536
+ }
6537
+ this.tabs.set(nextTabs);
6538
+ this.tabPreviewService.clearPreviews(closableKeySet);
6539
+ this.persistSession();
6540
+ }
6541
+ //#endregion
6542
+ //#region ---- Tab Close Helpers ----
6543
+ /**
6544
+ * Prompts before closing a cached background tab. Uses the page's {@link AXPTabCloseConfirm}
6545
+ * hook when available, otherwise falls back to the tab preview dirty flag.
6546
+ */
6547
+ async confirmBackgroundTabClose(key) {
6548
+ const instance = this.routeCacheRegistry.getComponentInstance(key);
6549
+ if (instance && typeof instance.onBeforeTabClose === 'function') {
6550
+ return instance.onBeforeTabClose();
6551
+ }
6552
+ return this.unsavedChangesConfirm.confirmIfDirty(this.tabPreviewService.isTabDirty(key), {
6553
+ approveLeaveOnConfirm: false,
6554
+ });
6555
+ }
6556
+ //#endregion
6557
+ //#region ---- Tab Close Navigation ----
6558
+ /**
6559
+ * Navigates away from the active tab before it is removed so canDeactivate runs while the tab is still visible.
6560
+ * Returns false when navigation was cancelled (e.g. unsaved-changes confirmation declined).
6561
+ */
6562
+ async navigateAwayBeforeClosingActiveTab(closedActiveIndex, remainingTabs) {
6563
+ const fallbackIndex = this.resolveFallbackTabIndex(closedActiveIndex, remainingTabs);
6564
+ const fallback = fallbackIndex >= 0 ? remainingTabs[fallbackIndex] : undefined;
6565
+ let targetKey;
6566
+ let targetUrl;
6567
+ if (fallback) {
6568
+ targetKey = fallback.key;
6569
+ targetUrl = fallback.url;
6570
+ }
6571
+ else {
6572
+ await this.ensureHomeTab();
6573
+ const home = this.tabs().find((item) => item.key === AXP_HOME_TAB_KEY);
6574
+ if (!home) {
6575
+ return false;
6576
+ }
6577
+ targetKey = home.key;
6578
+ targetUrl = home.url;
6579
+ }
6580
+ this.suppressNavigationSync = true;
6581
+ try {
6582
+ const navigated = await this.router.navigateByUrl(targetUrl);
6583
+ if (!navigated) {
6584
+ return false;
6585
+ }
6586
+ }
6587
+ finally {
6588
+ this.suppressNavigationSync = false;
6589
+ }
6590
+ this.activeTabKey.set(targetKey);
6591
+ this.touchTab(targetKey);
6592
+ return true;
6593
+ }
6594
+ /** Prefers the tab to the right after removal, then the tab to the left (browser-like). */
6595
+ resolveFallbackTabIndex(closedIndex, remainingTabs) {
6596
+ if (!remainingTabs.length) {
6597
+ return -1;
6598
+ }
6599
+ if (closedIndex < remainingTabs.length) {
6600
+ return closedIndex;
6601
+ }
6602
+ return closedIndex - 1;
6603
+ }
6604
+ //#endregion
6605
+ //#region ---- Route Cache Sync ----
6606
+ /**
6607
+ * Keeps the route cache registry aligned with the current navigation mode and the set of open
6608
+ * tabs. Cached page instances for closed/evicted tabs are destroyed by the registry.
6609
+ */
6610
+ bindRouteCacheSync() {
6611
+ effect(() => {
6612
+ this.routeCacheRegistry.setEnabled(this.effectiveMode() === 'tabular');
6613
+ this.routeCacheRegistry.syncOpenKeys(this.tabs().map((tab) => tab.key), this.tabs().map((tab) => tab.url));
6614
+ });
6615
+ }
6616
+ //#endregion
6617
+ //#region ---- Router Sync ----
6618
+ bindRouterEvents() {
6619
+ this.router.events
6620
+ .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
6621
+ .subscribe((event) => {
6622
+ void this.handleNavigationEnd(event.urlAfterRedirects);
6623
+ });
6624
+ }
6625
+ async handleNavigationEnd(url) {
6626
+ if (this.suppressNavigationSync) {
6627
+ return;
6628
+ }
6629
+ if (this.effectiveMode() === 'simple') {
6630
+ return;
6631
+ }
6632
+ const normalizedUrl = normalizeRouterUrl(url);
6633
+ if (!axpIsTabEligibleUrl(normalizedUrl)) {
6634
+ return;
6635
+ }
6636
+ await this.ensureHomeTab();
6637
+ if (this.isHomePageUrl(normalizedUrl)) {
6638
+ this.activateHomeTabForUrl(normalizedUrl);
6639
+ return;
6640
+ }
6641
+ const key = axpResolveTabKey(this.router, normalizedUrl);
6642
+ const existing = this.findTabForUrl(normalizedUrl);
6643
+ if (existing) {
6644
+ if (existing.key !== key) {
6645
+ this.migrateTabKey(existing.key, key);
6646
+ }
6647
+ this.updateTabUrl(key, normalizedUrl);
6648
+ this.touchTab(key);
6649
+ this.activeTabKey.set(key);
6650
+ this.persistSession();
6651
+ return;
6652
+ }
6653
+ this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
6654
+ this.activeTabKey.set(key);
6655
+ this.persistSession();
6656
+ }
6657
+ //#endregion
6658
+ //#region ---- Settings ----
6659
+ async loadSettings() {
6660
+ const [mode, maxTabs] = await Promise.all([
6661
+ this.settingsService.get(AXPCommonSettings.NavigationMode),
6662
+ this.settingsService.get(AXPCommonSettings.MaxOpenTabs),
6663
+ ]);
6664
+ this.navigationMode.set(mode === 'tabular' ? 'tabular' : 'simple');
6665
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
6666
+ await this.restoreSessionIfNeeded();
6667
+ }
6668
+ bindSettingsChanges() {
6669
+ this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
6670
+ if (event.keys.includes(AXPCommonSettings.NavigationMode)) {
6671
+ const raw = event.values[AXPCommonSettings.NavigationMode];
6672
+ const newMode = raw === 'tabular' ? 'tabular' : 'simple';
6673
+ if (newMode !== this.navigationMode()) {
6674
+ this.navigationMode.set(newMode);
6675
+ if (this.effectiveMode() === 'simple') {
6676
+ this.clearSession();
6677
+ }
6678
+ else {
6679
+ void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
6680
+ }
6681
+ }
6682
+ }
6683
+ if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
6684
+ this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
6685
+ this.enforceMaxTabs();
6686
+ this.persistSession();
6687
+ }
6688
+ });
6689
+ }
6690
+ bindSessionChanges() {
6691
+ this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
6692
+ void this.restoreSessionIfNeeded();
6693
+ });
6694
+ }
6695
+ //#endregion
6696
+ //#region ---- Tab Management ----
6697
+ async ensureHomeTab() {
6698
+ const homePath = this.resolveHomePath();
6699
+ if (!homePath) {
6700
+ return;
6701
+ }
6702
+ const homeUrl = normalizeRouterUrl(homePath);
6703
+ const homeKey = AXP_HOME_TAB_KEY;
6704
+ const existing = this.tabs().find((tab) => tab.key === homeKey);
6705
+ if (existing) {
6706
+ this.tabs.update((items) => items.map((tab) => tab.key === homeKey
6707
+ ? {
6708
+ ...tab,
6709
+ url: homeUrl,
6710
+ title: this.isGenericHomeTitle(tab.title) ? this.resolveHomeTitle() : tab.title,
6711
+ icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
6712
+ }
6713
+ : tab));
6714
+ return;
6715
+ }
6716
+ const now = Date.now();
6717
+ const homeTab = {
6718
+ key: homeKey,
6719
+ url: homeUrl,
6720
+ title: this.resolveHomeTitle(),
6721
+ icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
6722
+ pinned: true,
6723
+ closable: false,
6724
+ openedAt: now,
6725
+ lastActivatedAt: now,
6726
+ };
6727
+ this.tabs.update((items) => this.normalizeTabOrder([homeTab, ...items.filter((tab) => tab.key !== homeKey)]));
6728
+ }
6729
+ insertTab(tab) {
6730
+ if (this.isHomePageUrl(tab.url)) {
6731
+ this.activateHomeTabForUrl(tab.url);
6732
+ return;
6733
+ }
6734
+ this.tabs.update((items) => {
6735
+ const withoutDuplicate = items.filter((item) => {
6736
+ if (item.key === tab.key) {
6737
+ return false;
6738
+ }
6739
+ return axpResolveTabKey(this.router, item.url) !== tab.key;
6740
+ });
6741
+ return this.normalizeTabOrder([...withoutDuplicate, tab]);
6742
+ });
6743
+ this.enforceMaxTabs();
6744
+ }
6745
+ /** Home first; preserves session order for the remaining tabs. */
6746
+ ensureHomeTabFirst(items) {
6747
+ const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
6748
+ const others = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY);
6749
+ return home ? [home, ...others] : items;
6750
+ }
6751
+ getLastPinnedTabIndex(items) {
6752
+ for (let index = items.length - 1; index >= 0; index -= 1) {
6753
+ if (items[index]?.pinned) {
6754
+ return index;
6755
+ }
6756
+ }
6757
+ return 0;
6758
+ }
6759
+ /** Home first, then other pinned tabs, then unpinned tabs (stable by openedAt within each group). */
6760
+ normalizeTabOrder(items) {
6761
+ const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
6762
+ const pinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && tab.pinned);
6763
+ const unpinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && !tab.pinned);
6764
+ const byOpenedAt = (a, b) => a.openedAt - b.openedAt;
6765
+ return [...(home ? [home] : []), ...pinned.sort(byOpenedAt), ...unpinned.sort(byOpenedAt)];
6766
+ }
6767
+ enforceMaxTabs() {
6768
+ const limit = this.maxOpenTabs();
6769
+ let current = this.tabs();
6770
+ while (current.length > limit) {
6771
+ const evictionCandidate = [...current]
6772
+ .filter((tab) => tab.closable && !tab.pinned && tab.key !== this.activeTabKey())
6773
+ .filter((tab) => !this.tabPreviewService.isTabDirty(tab.key))
6774
+ .sort((a, b) => a.lastActivatedAt - b.lastActivatedAt)[0];
6775
+ if (!evictionCandidate) {
6776
+ break;
6777
+ }
6778
+ current = current.filter((tab) => tab.key !== evictionCandidate.key);
6779
+ }
6780
+ this.tabs.set(current);
6781
+ }
6782
+ createTabDescriptor(url, title, keyOverride) {
6783
+ const now = Date.now();
6784
+ const key = keyOverride ?? axpResolveTabKey(this.router, url);
6785
+ return {
6786
+ key,
6787
+ url,
6788
+ title: title ?? this.resolveTabTitle(url),
6789
+ icon: this.resolveTabIcon(url),
6790
+ pinned: false,
6791
+ closable: true,
6792
+ openedAt: now,
6793
+ lastActivatedAt: now,
6794
+ };
6795
+ }
6796
+ /** Finds a tab by scoped key or legacy full-path key for the same URL. */
6797
+ findTabForUrl(url) {
6798
+ if (this.isHomePageUrl(url)) {
6799
+ return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
6800
+ }
6801
+ const resolvedKey = axpResolveTabKey(this.router, url);
6802
+ return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
6803
+ (tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
6804
+ }
6805
+ /** True when the URL is the configured application home page (e.g. dashboard). */
6806
+ isHomePageUrl(url) {
6807
+ const homeUrl = this.resolveHomeUrl();
6808
+ if (!homeUrl) {
6809
+ return false;
6810
+ }
6811
+ return normalizeRouterUrl(url) === homeUrl;
6812
+ }
6813
+ resolveHomeUrl() {
6814
+ const homePath = this.resolveHomePath();
6815
+ if (!homePath) {
6816
+ return null;
6817
+ }
6818
+ return normalizeRouterUrl(homePath);
6819
+ }
6820
+ /** Activates the pinned home tab for a home-page URL instead of opening a duplicate tab. */
6821
+ activateHomeTabForUrl(url) {
6822
+ this.updateTabUrl(AXP_HOME_TAB_KEY, normalizeRouterUrl(url));
6823
+ this.touchTab(AXP_HOME_TAB_KEY);
6824
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
6825
+ this.persistSession();
6826
+ }
6827
+ /** Drops non-home tabs that duplicate the home page URL after session restore. */
6828
+ removeDuplicateHomeUrlTabs(tabs) {
6829
+ const homeUrl = this.resolveHomeUrl();
6830
+ if (!homeUrl) {
6831
+ return tabs;
6832
+ }
6833
+ return tabs.filter((tab) => tab.key === AXP_HOME_TAB_KEY || normalizeRouterUrl(tab.url) !== homeUrl);
6834
+ }
6835
+ /** Rewrites a tab key when route-scope resolution changes (e.g. profile sub-routes). */
6836
+ migrateTabKey(fromKey, toKey) {
6837
+ if (fromKey === toKey) {
6838
+ return;
6839
+ }
6840
+ if (this.activeTabKey() === fromKey) {
6841
+ this.activeTabKey.set(toKey);
6842
+ }
6843
+ this.tabs.update((items) => {
6844
+ const tab = items.find((item) => item.key === fromKey);
6845
+ if (!tab) {
6846
+ return items;
6847
+ }
6848
+ const without = items.filter((item) => item.key !== fromKey && item.key !== toKey);
6849
+ return this.normalizeTabOrder([...without, { ...tab, key: toKey }]);
6850
+ });
6851
+ }
6852
+ /** Collapses restored tabs that share the same scoped route key. */
6853
+ normalizeRestoredTabKeys(tabs) {
6854
+ const byResolvedKey = new Map();
6855
+ for (const tab of tabs) {
6856
+ if (tab.key === AXP_HOME_TAB_KEY) {
6857
+ byResolvedKey.set(AXP_HOME_TAB_KEY, tab);
6858
+ continue;
6859
+ }
6860
+ const resolvedKey = axpResolveTabKey(this.router, tab.url);
6861
+ const normalized = { ...tab, key: resolvedKey };
6862
+ const existing = byResolvedKey.get(resolvedKey);
6863
+ if (!existing || normalized.lastActivatedAt > existing.lastActivatedAt) {
6864
+ byResolvedKey.set(resolvedKey, normalized);
6865
+ }
6866
+ }
6867
+ return [...byResolvedKey.values()];
6868
+ }
6869
+ updateTabUrl(key, url) {
6870
+ this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, url } : tab)));
6871
+ }
6872
+ updateTabTitle(key, title) {
6873
+ this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.title !== title ? { ...tab, title } : tab)));
6874
+ this.persistSession();
6875
+ }
6876
+ updateTabIcon(key, icon) {
6877
+ this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.icon !== icon ? { ...tab, icon } : tab)));
6878
+ this.persistSession();
6879
+ }
6880
+ touchTab(key) {
6881
+ const now = Date.now();
6882
+ this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, lastActivatedAt: now } : tab)));
6883
+ }
6884
+ resolveTabTitle(url, fallback) {
6885
+ const match = axpFindMenuItemByRoute(this.menuService.items(), url);
6886
+ if (match?.item.text) {
6887
+ return match.item.text;
6888
+ }
6889
+ return fallback ?? axpFallbackTabTitle(url);
6890
+ }
6891
+ resolveTabIcon(url, fallback = 'fa-light fa-window') {
6892
+ const match = axpFindMenuItemByRoute(this.menuService.items(), url);
6893
+ if (match?.item.icon?.trim()) {
6894
+ return match.item.icon.trim();
6895
+ }
6896
+ return fallback;
6897
+ }
6898
+ resolveHomePath() {
6899
+ const current = this.homePageService.getCurrent();
6900
+ return current.path ? normalizeRouterUrl(current.path) : '';
6901
+ }
6902
+ resolveHomeTitle() {
6903
+ const current = this.homePageService.getCurrent();
6904
+ return current.title || 'Home';
6905
+ }
6906
+ isGenericHomeTitle(title) {
6907
+ if (title === '@general:terms.interface.navigation.home') {
6908
+ return true;
6909
+ }
6910
+ const homeTitle = this.resolveHomeTitle().trim();
6911
+ return title === homeTitle;
6912
+ }
6913
+ async syncCurrentRouteAsTab() {
6914
+ if (this.effectiveMode() === 'simple') {
6915
+ return;
6916
+ }
6917
+ await this.ensureHomeTab();
6918
+ await this.handleNavigationEnd(this.router.url);
6919
+ }
6920
+ normalizeMaxTabs(value) {
6921
+ const parsed = Number(value);
6922
+ if (!Number.isFinite(parsed)) {
6923
+ return 5;
6924
+ }
6925
+ return Math.min(20, Math.max(2, Math.round(parsed)));
6926
+ }
6927
+ //#endregion
6928
+ //#region ---- Session Persistence ----
6929
+ sessionStorageKey() {
6930
+ const userId = this.sessionService.user?.id;
6931
+ if (!userId) {
6932
+ return null;
6933
+ }
6934
+ return `${SESSION_STORAGE_PREFIX}(${userId})`;
6935
+ }
6936
+ persistSession() {
6937
+ const key = this.sessionStorageKey();
6938
+ if (!key || this.effectiveMode() === 'simple') {
6939
+ return;
6940
+ }
6941
+ const payload = {
6942
+ tabs: cloneDeep(this.tabs()),
6943
+ activeTabKey: this.activeTabKey(),
6944
+ };
6945
+ sessionStorage.setItem(key, JSON.stringify(payload));
6946
+ }
6947
+ clearSession() {
6948
+ const key = this.sessionStorageKey();
6949
+ if (key) {
6950
+ sessionStorage.removeItem(key);
6951
+ }
6952
+ this.tabs.set([]);
6953
+ this.activeTabKey.set(null);
6954
+ this.tabPreviewService.clearAll();
6955
+ }
6956
+ async restoreSessionIfNeeded() {
6957
+ if (this.effectiveMode() === 'simple') {
6958
+ return;
6959
+ }
6960
+ const storageKey = this.sessionStorageKey();
6961
+ if (!storageKey) {
6962
+ await this.ensureHomeTab();
6963
+ await this.syncCurrentRouteAsTab();
6964
+ return;
6965
+ }
6966
+ const raw = sessionStorage.getItem(storageKey);
6967
+ if (!raw) {
6968
+ await this.ensureHomeTab();
6969
+ await this.syncCurrentRouteAsTab();
6970
+ return;
6971
+ }
6972
+ try {
6973
+ const parsed = JSON.parse(raw);
6974
+ const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
6975
+ this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
6976
+ await this.ensureHomeTab();
6977
+ const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
6978
+ this.activeTabKey.set(activeKey);
6979
+ const activeTab = this.tabs().find((tab) => tab.key === activeKey);
6980
+ if (activeTab && normalizeRouterUrl(this.router.url) !== normalizeRouterUrl(activeTab.url)) {
6981
+ this.suppressNavigationSync = true;
6982
+ try {
6983
+ await this.router.navigateByUrl(activeTab.url);
6984
+ }
6985
+ finally {
6986
+ this.suppressNavigationSync = false;
6987
+ }
6988
+ }
6989
+ }
6990
+ catch {
6991
+ await this.ensureHomeTab();
6992
+ await this.syncCurrentRouteAsTab();
6993
+ }
6994
+ }
6995
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6996
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, providedIn: 'root' }); }
6997
+ }
6998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, decorators: [{
6999
+ type: Injectable,
7000
+ args: [{
7001
+ providedIn: 'root',
7002
+ }]
7003
+ }], ctorParameters: () => [] });
7004
+
7005
+ //TODO Loading, Redirect, Drawer, Show toast
7006
+ const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
7007
+ const AXPRefreshEvent = createWorkFlowEvent('Refresh Event Fired');
7008
+ const AXPReloadEvent = createWorkFlowEvent('Reload Event Fired');
7009
+ class AXPWorkflowNavigateAction extends AXPWorkflowAction {
7010
+ constructor() {
7011
+ super(...arguments);
7012
+ this.router = inject(Router);
7013
+ this.tabNavigation = inject(AXPTabNavigationService);
7014
+ }
7015
+ async execute(context) {
7016
+ const payload = context.getVariable('payload');
7017
+ if (Array.isArray(payload.commands)) {
7018
+ await this.router.navigate(payload.commands, payload.extras);
7019
+ return;
7020
+ }
7021
+ const path = payload.commands;
7022
+ if (!path) {
7023
+ return;
7024
+ }
7025
+ if (path.toLowerCase().startsWith('http') || payload.target === 'blank') {
7026
+ window.open(path, '_blank');
7027
+ return;
7028
+ }
7029
+ await this.tabNavigation.navigate(path, payload.extras);
7030
+ }
7031
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
7032
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
7033
+ }
7034
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, decorators: [{
7035
+ type: Injectable
7036
+ }] });
7037
+ class AXPDialogConfirmAction extends AXPWorkflowAction {
7038
+ constructor() {
7039
+ super(...arguments);
7040
+ this.dialogService = inject(AXDialogService);
7041
+ this.translationService = inject(AXTranslationService);
7042
+ this.defaultAction = 'cancel';
7043
+ }
7044
+ async execute(context) {
7045
+ console.log('AXPDialogConfirmAction', this.title, this.message, this.type);
7046
+ context.setOutput('result', false);
7047
+ const dialogResult = await this.dialogService.confirm(await this.translationService.translateAsync(this.title), await this.translationService.translateAsync(this.message), (await this.translationService.translateAsync(this.type)), 'horizontal', false, 'cancel');
7048
+ context.setOutput('result', dialogResult.result);
7049
+ }
7050
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
7051
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction }); }
7052
+ }
7053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, decorators: [{
7054
+ type: Injectable
7055
+ }] });
7056
+ class AXPToastAction extends AXPWorkflowAction {
7057
+ constructor() {
7058
+ super(...arguments);
7059
+ this.toastService = inject(AXToastService);
7060
+ this.translationService = inject(AXTranslationService);
7061
+ this.settingsService = inject(AXPSettingsService);
5838
7062
  }
5839
7063
  async execute(context) {
5840
7064
  const showResult = await this.settingsService.get(AXPCommonSettings.EnableOperationToasts);
@@ -5869,7 +7093,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
5869
7093
  class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
5870
7094
  constructor() {
5871
7095
  super(...arguments);
5872
- this.router = inject(Router);
7096
+ this.tabNavigation = inject(AXPTabNavigationService);
5873
7097
  }
5874
7098
  async execute(context) {
5875
7099
  const command = context.getVariable();
@@ -5882,7 +7106,7 @@ class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
5882
7106
  window.open(path, '_blank');
5883
7107
  }
5884
7108
  else {
5885
- await axpNavigateAppPath(this.router, path, command.options.extras);
7109
+ await this.tabNavigation.navigate(path, command.options.extras);
5886
7110
  }
5887
7111
  }
5888
7112
  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 +7180,14 @@ class AXPCommonModule {
5956
7180
  /**
5957
7181
  * @ignore
5958
7182
  */
5959
- constructor(instances, _keyboardShortcutsHelp) {
7183
+ constructor(instances, _keyboardShortcutsHelp, formatterRegistry) {
7184
+ formatterRegistry.register(AXPTranslateFormatter);
5960
7185
  instances.forEach((f) => {
5961
7186
  f();
5962
7187
  });
5963
7188
  }
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,
7189
+ 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 }); }
7190
+ 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
7191
  AXDateTimeModule,
5967
7192
  AXToastModule,
5968
7193
  AXPModuleManifestModule], exports: [RouterModule] }); }
@@ -6009,7 +7234,7 @@ class AXPCommonModule {
6009
7234
  provide: AXP_SETTING_DEFINITION_PROVIDER,
6010
7235
  useFactory: async () => {
6011
7236
  const injector = inject(Injector);
6012
- const provider = (await import('./acorex-platform-common-common-settings.provider-Ytey9uhY.mjs')).AXPCommonSettingProvider;
7237
+ const provider = (await import('./acorex-platform-common-common-settings.provider-ChoT4m4k.mjs')).AXPCommonSettingProvider;
6013
7238
  return new provider(injector);
6014
7239
  },
6015
7240
  multi: true,
@@ -6110,7 +7335,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6110
7335
  provide: AXP_SETTING_DEFINITION_PROVIDER,
6111
7336
  useFactory: async () => {
6112
7337
  const injector = inject(Injector);
6113
- const provider = (await import('./acorex-platform-common-common-settings.provider-Ytey9uhY.mjs')).AXPCommonSettingProvider;
7338
+ const provider = (await import('./acorex-platform-common-common-settings.provider-ChoT4m4k.mjs')).AXPCommonSettingProvider;
6114
7339
  return new provider(injector);
6115
7340
  },
6116
7341
  multi: true,
@@ -6120,7 +7345,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6120
7345
  }], ctorParameters: () => [{ type: undefined, decorators: [{
6121
7346
  type: Inject,
6122
7347
  args: ['AXPCommonModuleFactory']
6123
- }] }, { type: AXPKeyboardShortcutsHelpService }] });
7348
+ }] }, { type: AXPKeyboardShortcutsHelpService }, { type: i2$1.AXFormatterRegistryService }] });
6124
7349
 
6125
7350
  const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
6126
7351
  const AXP_PLATFORM_CONFIG_TOKEN = new InjectionToken('AXP_PLATFORM_CONFIG_TOKEN', {
@@ -6335,18 +7560,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6335
7560
  type: Injectable
6336
7561
  }] });
6337
7562
 
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
7563
  //#region ---- Injection Token ----
6351
7564
  /**
6352
7565
  * Injection token for status providers
@@ -6558,356 +7771,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6558
7771
  args: [{ providedIn: 'root' }]
6559
7772
  }] });
6560
7773
 
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
- }
7774
+ //#region ---- Types ----
7775
+ //#endregion
6911
7776
 
6912
7777
  class AXPClipBoardService {
6913
7778
  constructor() {
@@ -7590,5 +8455,5 @@ class AXPVersioningService {
7590
8455
  * Generated bundle index. Do not edit.
7591
8456
  */
7592
8457
 
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 };
8458
+ 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, 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_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, 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, 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, buildEntitySearchTitleContext, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, findManualPresetIdByOperation, formatEntityRowDisplayTitle, inferSettingCoercionKind, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, normalizeEntityDisplayTemplate, normalizePositiveNumberOption, normalizeRouterUrl, peelSettingValueKey, printGraphicFromElement, provideDynamicHomePage, provideMenuMiddleware, resolveDateFilterPresets, resolveEntityModifyPopupTitle, resolveEntityModifyRecordTitleTemplate, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntityRowTitleTemplate, resolveEntitySearchTitleFallback, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, sanitizeResolvedEntityDisplayTitle, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, triggerBrowserDownload, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
7594
8459
  //# sourceMappingURL=acorex-platform-common.mjs.map