@acorex-platform/framework-client 2.0.0-next.3 → 2.0.0-next.31

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 (81) hide show
  1. package/fesm2022/acorex-platform-framework-client-auth.mjs +8 -9
  2. package/fesm2022/acorex-platform-framework-client-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs → acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs} +35 -2
  4. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-framework-client-common.mjs +1387 -538
  6. package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-framework-client-core.mjs +7 -2
  8. package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +20 -0
  10. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-framework-client-layout-components.mjs +520 -142
  12. package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -1
  13. package/fesm2022/{acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DaOvPoNI.mjs → acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DmkFxSuJ.mjs} +1084 -223
  14. package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DmkFxSuJ.mjs.map +1 -0
  15. package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BU7KEfyF.mjs → acorex-platform-framework-client-layout-entity-attachments-page.component-BV5WLQVo.mjs} +2 -2
  16. package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BU7KEfyF.mjs.map → acorex-platform-framework-client-layout-entity-attachments-page.component-BV5WLQVo.mjs.map} +1 -1
  17. package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-Boq0MUFK.mjs → acorex-platform-framework-client-layout-entity-bulk-entity.command-O8zdiHRH.mjs} +2 -2
  18. package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-Boq0MUFK.mjs.map → acorex-platform-framework-client-layout-entity-bulk-entity.command-O8zdiHRH.mjs.map} +1 -1
  19. package/fesm2022/{acorex-platform-framework-client-layout-entity-file-list-popup.component-CMoFkzhf.mjs → acorex-platform-framework-client-layout-entity-file-list-popup.component-B3-0gJTQ.mjs} +4 -4
  20. package/fesm2022/{acorex-platform-framework-client-layout-entity-file-list-popup.component-CMoFkzhf.mjs.map → acorex-platform-framework-client-layout-entity-file-list-popup.component-B3-0gJTQ.mjs.map} +1 -1
  21. package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-Bre8friL.mjs → acorex-platform-framework-client-layout-entity-gallery-page.component-CjfLUBPZ.mjs} +2 -2
  22. package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-Bre8friL.mjs.map → acorex-platform-framework-client-layout-entity-gallery-page.component-CjfLUBPZ.mjs.map} +1 -1
  23. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +1 -1
  24. package/fesm2022/acorex-platform-framework-client-layout-views.mjs +94 -17
  25. package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -1
  26. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +285 -184
  27. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -1
  28. package/fesm2022/{acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-1UJh0Qj4.mjs → acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-CpmGqDUE.mjs} +1167 -265
  29. package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-CpmGqDUE.mjs.map +1 -0
  30. package/fesm2022/acorex-platform-framework-client-layout-widgets-filter-default-operators-widget-edit.component-DOEn58--.mjs +103 -0
  31. package/fesm2022/acorex-platform-framework-client-layout-widgets-filter-default-operators-widget-edit.component-DOEn58--.mjs.map +1 -0
  32. package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-SHCgo4UC.mjs → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-XgjDH9Oc.mjs} +3 -3
  33. package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-SHCgo4UC.mjs.map → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-XgjDH9Oc.mjs.map} +1 -1
  34. package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYsrp8LT.mjs → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DDczHmAA.mjs} +2 -2
  35. package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYsrp8LT.mjs.map → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DDczHmAA.mjs.map} +1 -1
  36. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +1 -1
  37. package/fesm2022/acorex-platform-framework-client-realtime.mjs +49 -7
  38. package/fesm2022/acorex-platform-framework-client-realtime.mjs.map +1 -1
  39. package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-create-view.component-DwCoz2Du.mjs → acorex-platform-framework-client-themes-default-entity-master-create-view.component-DPU6jTDk.mjs} +8 -2
  40. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DPU6jTDk.mjs.map +1 -0
  41. package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BCyFK60f.mjs → acorex-platform-framework-client-themes-default-entity-master-modify-view.component-CLdQJ8e8.mjs} +8 -2
  42. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-CLdQJ8e8.mjs.map +1 -0
  43. package/fesm2022/{acorex-platform-framework-client-themes-default-error-401.component-1lfBpY_d.mjs → acorex-platform-framework-client-themes-default-error-401.component-DszlyIaw.mjs} +5 -11
  44. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-DszlyIaw.mjs.map +1 -0
  45. package/fesm2022/{acorex-platform-framework-client-themes-default-error-404.component-BeWyRCJr.mjs → acorex-platform-framework-client-themes-default-error-404.component-BusKNAit.mjs} +5 -11
  46. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-BusKNAit.mjs.map +1 -0
  47. package/fesm2022/{acorex-platform-framework-client-themes-default-error-offline.component-PvveUj5c.mjs → acorex-platform-framework-client-themes-default-error-offline.component-Abf14p44.mjs} +15 -4
  48. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-Abf14p44.mjs.map +1 -0
  49. package/fesm2022/acorex-platform-framework-client-themes-default-error-page-go-home-B9VmB9Vt.mjs +19 -0
  50. package/fesm2022/acorex-platform-framework-client-themes-default-error-page-go-home-B9VmB9Vt.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-framework-client-themes-default.mjs +306 -129
  52. package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -1
  53. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +11 -5
  54. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -1
  55. package/i18n/en-AU/general.json +32 -4
  56. package/i18n/en-AU/layout.json +2 -1
  57. package/i18n/en-AU/platform-layout-widgets.json +7 -1
  58. package/i18n/en-US/general.json +32 -4
  59. package/i18n/en-US/layout.json +2 -1
  60. package/i18n/en-US/platform-layout-widgets.json +7 -1
  61. package/i18n/fa-IR/general.json +32 -4
  62. package/i18n/fa-IR/layout.json +2 -1
  63. package/i18n/fa-IR/platform-layout-widgets.json +7 -1
  64. package/package.json +6 -6
  65. package/types/acorex-platform-framework-client-auth.d.ts +21 -11
  66. package/types/acorex-platform-framework-client-common.d.ts +230 -14
  67. package/types/acorex-platform-framework-client-layout-components.d.ts +47 -6
  68. package/types/acorex-platform-framework-client-layout-entity.d.ts +187 -35
  69. package/types/acorex-platform-framework-client-layout-views.d.ts +15 -0
  70. package/types/acorex-platform-framework-client-layout-widget-core.d.ts +64 -14
  71. package/types/acorex-platform-framework-client-layout-widgets.d.ts +145 -9
  72. package/types/acorex-platform-framework-client-realtime.d.ts +5 -0
  73. package/types/acorex-platform-framework-client-themes-default.d.ts +16 -5
  74. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs.map +0 -1
  75. package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DaOvPoNI.mjs.map +0 -1
  76. package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-1UJh0Qj4.mjs.map +0 -1
  77. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DwCoz2Du.mjs.map +0 -1
  78. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BCyFK60f.mjs.map +0 -1
  79. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-1lfBpY_d.mjs.map +0 -1
  80. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-BeWyRCJr.mjs.map +0 -1
  81. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-PvveUj5c.mjs.map +0 -1
@@ -1,17 +1,17 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, Injector, signal, ApplicationRef, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, ChangeDetectionStrategy, ViewEncapsulation, Component, output, effect, viewChild, afterNextRender, untracked, ErrorHandler, EventEmitter, Input, Output, Directive, Inject, runInInjectionContext, model, linkedSignal } from '@angular/core';
3
- import * as i5 from '@angular/router';
4
- import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, UrlTree, RouterModule } from '@angular/router';
5
3
  import { AXPSessionService, AXPSessionStatus, AXPUnauthenticatedError, AXPAuthGuard, commandMessageTextForError } from '@acorex-platform/framework-client/auth';
6
4
  export { commandMessageTextForError } from '@acorex-platform/framework-client/auth';
7
- import { combineLatest, map, distinctUntilChanged, Subject, ReplaySubject, firstValueFrom, filter, take, timer, timeout, switchMap, from, of } from 'rxjs';
8
- import { unwrapValueProperty, isLocaleStringMap, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, runtimeQueryPayloadData, runtimeCommandPayloadData, AXPRegionalSetting, AXPWidgetsCatalog, axpResolveUserNotificationDelivery, resolveMultiLanguageString, coerceUnknownToMultiLanguageString, AXPKeyboardShortcutPriority, AXP_PLATFORM_ERROR_CODES, AXPRequestError, mapUnknownToAXPErrorBody, AXPSystemActionType, runtimeQueryCollectionData, createRandomUuid, runtimeCommandFailure, runtimeCommandSuccessObject, runtimeQuerySuccessObject } from '@acorex-platform/framework-shared/core';
9
5
  import * as i4$2 from '@acorex-platform/framework-client/core';
10
6
  import { AXPDebugService, AXPBroadcastEventService, AXP_SETTINGS_DEFINITION_DIALOG_OPENER, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, normalizeKeyboardShortcuts, AXPDeviceService, provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPModuleManifestModule, AXPComponentSlotModule, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_LOGO_IMAGE_SRC_RESOLVER, AXPHookService, AXPPageContextRegistry } from '@acorex-platform/framework-client/core';
11
7
  export { AXPDebugService } from '@acorex-platform/framework-client/core';
8
+ import { unwrapValueProperty, isLocaleStringMap, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, runtimeQueryPayloadData, runtimeCommandPayloadData, AXPRegionalSetting, AXPWidgetsCatalog, axpResolveUserNotificationDelivery, resolveMultiLanguageString, coerceUnknownToMultiLanguageString, AXPKeyboardShortcutPriority, AXP_PLATFORM_ERROR_CODES, AXPRequestError, mapUnknownToAXPErrorBody, AXPSystemActionType, runtimeQueryCollectionData, createRandomUuid, runtimeCommandFailure, runtimeCommandSuccessObject, runtimeQuerySuccessObject } from '@acorex-platform/framework-shared/core';
12
9
  import { merge, sortBy, isString, toString, isPlainObject, mergeWith, defaultTo, isEmpty, isEqual, cloneDeep, get, isNumber, isNil, isObject, omit, uniq } from 'lodash-es';
10
+ import { combineLatest, map, distinctUntilChanged, Subject, ReplaySubject, firstValueFrom, filter, take, timer, timeout, switchMap, from, of } from 'rxjs';
13
11
  import { AXPQueryService, AXPCommandService, provideCommandSetups, provideQuerySetups } from '@acorex-platform/framework-client/runtime';
14
12
  import Dexie from 'dexie';
13
+ import * as i5 from '@angular/router';
14
+ import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, UrlTree, RouterModule } from '@angular/router';
15
15
  import { AXDialogService } from '@acorex/components/dialog';
16
16
  import * as i6 from '@acorex/core/translation';
17
17
  import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString as resolveMultiLanguageString$1 } from '@acorex/core/translation';
@@ -34,7 +34,7 @@ import { AXProgressBarModule } from '@acorex/components/progress-bar';
34
34
  import * as i3$1 from '@acorex/core/format';
35
35
  import { AXFormatModule, AXFormatService } from '@acorex/core/format';
36
36
  import { AXNotificationService, AXNotificationModule } from '@acorex/components/notification';
37
- import { filter as filter$1 } from 'rxjs/operators';
37
+ import { filter as filter$1, map as map$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
38
38
  import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
39
39
  import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
40
40
  import { AXToastModule, AXToastService } from '@acorex/components/toast';
@@ -238,162 +238,109 @@ const ALL_DEFAULT_OPERATORS = [
238
238
  BETWEEN_OPER,
239
239
  ];
240
240
 
241
- const DEFAULT_DATE_FILTER_PRESETS = [
242
- { id: 'today', title: '@general:terms.date-presets.today', kind: 'computed' },
243
- { id: 'this-week', title: '@general:terms.date-presets.this-week', kind: 'computed' },
244
- { id: 'this-month', title: '@general:terms.date-presets.this-month', kind: 'computed' },
245
- { id: 'yesterday', title: '@general:terms.date-presets.yesterday', kind: 'computed' },
246
- { id: 'last-week', title: '@general:terms.date-presets.last-week', kind: 'computed' },
247
- { id: 'last-month', title: '@general:terms.date-presets.last-month', kind: 'computed' },
248
- { id: 'tomorrow', title: '@general:terms.date-presets.tomorrow', kind: 'computed' },
249
- { id: 'next-week', title: '@general:terms.date-presets.next-week', kind: 'computed' },
250
- { id: 'next-month', title: '@general:terms.date-presets.next-month', kind: 'computed' },
251
- { id: 'between', title: BETWEEN_OPER.title, kind: 'manual', operationType: 'between' },
252
- { id: 'greater-than', title: '@general:terms.operators.greater-than', kind: 'manual', operationType: 'greaterThan' },
253
- { id: 'less-than', title: '@general:terms.operators.less-than', kind: 'manual', operationType: 'lessThan' },
241
+ //#endregion
242
+ //#region ----- Catalog -----
243
+ const I18N$1 = '@general:settings.experience.entity.filter-default-operators.widgets';
244
+ /** Single-value choice filters. `in` stays on the multiple-selection path. */
245
+ const EQUALITY_OPERATORS = [EQ_OPER, NOT_EQ_OPER];
246
+ function toChoices(operators) {
247
+ return operators.map((operator) => ({ name: operator.name, titleKey: operator.title }));
248
+ }
249
+ /**
250
+ * One row per filter widget. `codeDefault` matches the widget config constant
251
+ * (`AXP_*_FILTER_DEFAULT_OPERATOR`). Stored settings keep only values that differ from it.
252
+ */
253
+ const AXP_FILTER_DEFAULT_OPERATOR_WIDGETS = [
254
+ {
255
+ widgetName: 'string-filter',
256
+ titleKey: `${I18N$1}.string-filter`,
257
+ codeDefault: 'contains',
258
+ operators: toChoices(STRING_OPERATORS),
259
+ },
260
+ {
261
+ widgetName: 'number-filter',
262
+ titleKey: `${I18N$1}.number-filter`,
263
+ codeDefault: 'equal',
264
+ operators: toChoices(NUMBER_OPERATORS),
265
+ },
266
+ {
267
+ widgetName: 'datetime-filter',
268
+ titleKey: `${I18N$1}.datetime-filter`,
269
+ codeDefault: 'equal',
270
+ operators: toChoices(DATE_OPERATORS),
271
+ },
272
+ {
273
+ widgetName: 'boolean-filter',
274
+ titleKey: `${I18N$1}.boolean-filter`,
275
+ codeDefault: 'equal',
276
+ operators: toChoices(BOOLEAN_OPERATORS),
277
+ },
278
+ {
279
+ widgetName: 'select-filter',
280
+ titleKey: `${I18N$1}.select-filter`,
281
+ codeDefault: 'equal',
282
+ operators: toChoices(EQUALITY_OPERATORS),
283
+ },
284
+ {
285
+ widgetName: 'lookup-filter',
286
+ titleKey: `${I18N$1}.lookup-filter`,
287
+ codeDefault: 'equal',
288
+ operators: toChoices(EQUALITY_OPERATORS),
289
+ },
290
+ {
291
+ widgetName: 'status-filter',
292
+ titleKey: `${I18N$1}.status-filter`,
293
+ codeDefault: 'equal',
294
+ operators: toChoices(EQUALITY_OPERATORS),
295
+ },
296
+ {
297
+ widgetName: 'time-duration-filter',
298
+ titleKey: `${I18N$1}.time-duration-filter`,
299
+ codeDefault: 'equal',
300
+ operators: toChoices(NUMBER_OPERATORS),
301
+ },
254
302
  ];
255
- function resolveDateFilterPresets(customPresets, presetIds) {
256
- let presets = customPresets?.length ? customPresets : DEFAULT_DATE_FILTER_PRESETS;
257
- if (presetIds?.length) {
258
- presets = presets.filter((p) => presetIds.includes(p.id));
303
+ const AXP_FILTER_DEFAULT_OPERATORS_WIDGET_NAME = 'filter-default-operators';
304
+ //#endregion
305
+ //#region ----- Map helpers -----
306
+ function readFilterDefaultOperatorMap(value) {
307
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
308
+ return {};
259
309
  }
260
- return presets;
261
- }
262
- function applyDateFilterPreset(presetId, calendarService) {
263
- const currentDate = calendarService.now().date;
264
- const calendar = calendarService.calendar;
265
- switch (presetId) {
266
- case 'today':
267
- return {
268
- operation: { type: 'between' },
269
- value: {
270
- from: calendar.startOf(currentDate, 'day').date.toISOString(),
271
- to: calendar.endOf(currentDate, 'day').date.toISOString(),
272
- },
273
- };
274
- case 'this-week':
275
- return {
276
- operation: { type: 'between' },
277
- value: {
278
- from: calendar.startOf(currentDate, 'week').date.toISOString(),
279
- to: calendar.endOf(currentDate, 'week').date.toISOString(),
280
- },
281
- };
282
- case 'this-month':
283
- return {
284
- operation: { type: 'between' },
285
- value: {
286
- from: calendar.startOf(currentDate, 'month').date.toISOString(),
287
- to: calendar.endOf(currentDate, 'month').date.toISOString(),
288
- },
289
- };
290
- case 'yesterday': {
291
- const yesterday = calendar.add(currentDate, 'day', -1);
292
- return {
293
- operation: { type: 'between' },
294
- value: {
295
- from: yesterday.startOf('day').date.toISOString(),
296
- to: yesterday.endOf('day').date.toISOString(),
297
- },
298
- };
299
- }
300
- case 'last-week': {
301
- const lastWeek = calendar.add(currentDate, 'week', -1);
302
- return {
303
- operation: { type: 'between' },
304
- value: {
305
- from: lastWeek.startOf('week').date.toISOString(),
306
- to: lastWeek.endOf('week').date.toISOString(),
307
- },
308
- };
309
- }
310
- case 'last-month': {
311
- const lastMonth = calendar.add(currentDate, 'month', -1);
312
- return {
313
- operation: { type: 'between' },
314
- value: {
315
- from: lastMonth.startOf('month').date.toISOString(),
316
- to: lastMonth.endOf('month').date.toISOString(),
317
- },
318
- };
319
- }
320
- case 'tomorrow': {
321
- const tomorrow = calendar.add(currentDate, 'day', 1);
322
- return {
323
- operation: { type: 'between' },
324
- value: {
325
- from: tomorrow.startOf('day').date.toISOString(),
326
- to: tomorrow.endOf('day').date.toISOString(),
327
- },
328
- };
329
- }
330
- case 'next-week': {
331
- const nextWeek = calendar.add(currentDate, 'week', 1);
332
- return {
333
- operation: { type: 'between' },
334
- value: {
335
- from: nextWeek.startOf('week').date.toISOString(),
336
- to: nextWeek.endOf('week').date.toISOString(),
337
- },
338
- };
339
- }
340
- case 'next-month': {
341
- const nextMonth = calendar.add(currentDate, 'month', 1);
342
- return {
343
- operation: { type: 'between' },
344
- value: {
345
- from: nextMonth.startOf('month').date.toISOString(),
346
- to: nextMonth.endOf('month').date.toISOString(),
347
- },
348
- };
310
+ const result = {};
311
+ for (const [widgetName, operatorName] of Object.entries(value)) {
312
+ if (typeof operatorName === 'string' && operatorName.length > 0) {
313
+ result[widgetName] = operatorName;
349
314
  }
350
- case 'between':
351
- return { operation: { type: 'between' } };
352
- case 'greater-than':
353
- return { operation: { type: 'greaterThan' } };
354
- case 'less-than':
355
- return { operation: { type: 'lessThan' } };
356
- default:
357
- return {
358
- operation: { type: 'between' },
359
- value: { from: '', to: '' },
360
- };
361
315
  }
316
+ return result;
362
317
  }
363
- function findManualPresetIdByOperation(operationType, presets) {
364
- if (!operationType) {
365
- return undefined;
366
- }
367
- return presets.find((p) => p.kind === 'manual' && p.operationType === operationType)?.id;
318
+ function findFilterDefaultOperatorWidget(widgetName) {
319
+ return AXP_FILTER_DEFAULT_OPERATOR_WIDGETS.find((widget) => widget.widgetName === widgetName);
368
320
  }
369
-
370
- const AXP_HOME_PAGES = new InjectionToken('AXP_HOME_PAGES');
371
- const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY', {
372
- factory: () => 'home'
373
- });
374
-
375
- /** Strict descendants allowed to override a setting owned at `scope`. */
376
- const AXP_SETTING_SCOPE_DESCENDANTS = {
377
- P: ['T', 'U'],
378
- T: ['U'],
379
- U: [],
380
- };
381
- function normalizeOverridableAt(ownerScope, overridableAt) {
382
- const scopes = overridableAt ?? [];
383
- const allowed = AXP_SETTING_SCOPE_DESCENDANTS[ownerScope];
384
- for (const scope of scopes) {
385
- if (!allowed.includes(scope)) {
386
- throw new Error(`Setting at scope "${ownerScope}" cannot be overridable at "${scope}". Allowed: [${allowed.join(', ')}].`);
387
- }
321
+ /** Stored operator when it belongs to that widget; otherwise the code default. */
322
+ function resolveStoredFilterDefaultOperator(widgetName, stored, codeDefault) {
323
+ const selected = readFilterDefaultOperatorMap(stored)[widgetName];
324
+ if (!selected) {
325
+ return codeDefault;
388
326
  }
389
- return scopes;
327
+ const widget = findFilterDefaultOperatorWidget(widgetName);
328
+ if (widget && !widget.operators.some((operator) => operator.name === selected)) {
329
+ return codeDefault;
330
+ }
331
+ return selected;
390
332
  }
391
- function isSettingVisibleOnScope(setting, pageScope) {
392
- if (setting.scope === pageScope) {
393
- return true;
333
+ /** Persist a change only when it differs from the widget's code default. */
334
+ function writeFilterDefaultOperatorOverride(current, widgetName, operatorName, codeDefault) {
335
+ const next = { ...readFilterDefaultOperatorMap(current) };
336
+ if (!operatorName || operatorName === codeDefault) {
337
+ delete next[widgetName];
338
+ return next;
394
339
  }
395
- return (setting.overridableAt ?? []).includes(pageScope);
340
+ next[widgetName] = operatorName;
341
+ return next;
396
342
  }
343
+ //#endregion
397
344
 
398
345
  var AXPCommonSettings;
399
346
  (function (AXPCommonSettings) {
@@ -413,6 +360,11 @@ var AXPCommonSettings;
413
360
  * IndexedDB platform-settings cache. When false, bootstrap redirects to /auth/unavailable.
414
361
  */
415
362
  AXPCommonSettings["AllowWorkingOffline"] = "Common:Setting:Offline.AllowWorkingOffline";
363
+ /**
364
+ * Platform (P): when false, hide user availability (account menu, avatar dot, user card).
365
+ * Products without a status surface turn this off.
366
+ */
367
+ AXPCommonSettings["UserStatusEnabled"] = "Common:Setting:Presence.UserStatusEnabled";
416
368
  AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
417
369
  /** When true, the entity master-list table derives header bands from entity sections (property groups). */
418
370
  AXPCommonSettings["EntityMultiLevelHeaders"] = "Common:Setting:Entity.MultiLevelHeaders";
@@ -423,6 +375,8 @@ var AXPCommonSettings;
423
375
  AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
424
376
  /** When true with entity list quick-search input, filters emit {@link AXP_QUERY_QUICK_SEARCH_FIELD} instead of inline OR. */
425
377
  AXPCommonSettings["QuickSearchEnabled"] = "Common:Setting:Entity.QuickSearchEnabled";
378
+ /** Platform map of filter widget name → operator. Only overrides are stored. */
379
+ AXPCommonSettings["FilterDefaultOperators"] = "Common:Setting:Entity.FilterDefaultOperators";
426
380
  })(AXPCommonSettings || (AXPCommonSettings = {}));
427
381
  /**
428
382
  * File-chooser keys live here so `common` can register Experience settings
@@ -445,6 +399,31 @@ const AXP_RUNTIME_QUERY_CACHE_DEFAULTS = {
445
399
  };
446
400
  /** Default entity list presentation when the user has not chosen a layout. */
447
401
  const AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT = 'table';
402
+ /** User status stays visible until a platform setting turns it off. */
403
+ const AXP_USER_STATUS_ENABLED_DEFAULT = true;
404
+
405
+ /** Strict descendants allowed to override a setting owned at `scope`. */
406
+ const AXP_SETTING_SCOPE_DESCENDANTS = {
407
+ P: ['T', 'U'],
408
+ T: ['U'],
409
+ U: [],
410
+ };
411
+ function normalizeOverridableAt(ownerScope, overridableAt) {
412
+ const scopes = overridableAt ?? [];
413
+ const allowed = AXP_SETTING_SCOPE_DESCENDANTS[ownerScope];
414
+ for (const scope of scopes) {
415
+ if (!allowed.includes(scope)) {
416
+ throw new Error(`Setting at scope "${ownerScope}" cannot be overridable at "${scope}". Allowed: [${allowed.join(', ')}].`);
417
+ }
418
+ }
419
+ return scopes;
420
+ }
421
+ function isSettingVisibleOnScope(setting, pageScope) {
422
+ if (setting.scope === pageScope) {
423
+ return true;
424
+ }
425
+ return (setting.overridableAt ?? []).includes(pageScope);
426
+ }
448
427
 
449
428
  class AXPSettingDefinitionProviderContext {
450
429
  constructor() {
@@ -584,24 +563,64 @@ const AXP_SETTING_DEFINITION_PROVIDER = new InjectionToken('AXP_SETTING_DEFINITI
584
563
  class AXPSettingDefinitionProviderService {
585
564
  constructor() {
586
565
  this.providers = inject(AXP_SETTING_DEFINITION_PROVIDER, { optional: true });
566
+ this.sessionService = inject(AXPSessionService);
587
567
  this.cache = null;
568
+ /**
569
+ * Tenant, application, and enabled edition features.
570
+ * Providers such as Telegram gate sections on `isModuleEnabled` at build time,
571
+ * so a catalog built before login must be rebuilt once features arrive.
572
+ */
573
+ this.cacheAvailabilityKey = null;
588
574
  this.loadPromise = null;
589
575
  }
576
+ /** Same identity permissions use when deciding the definition cache is still valid. */
577
+ getAvailabilityKey() {
578
+ return this.buildAvailabilityKey();
579
+ }
580
+ buildAvailabilityKey() {
581
+ const enabled = this.sessionService.features
582
+ .filter((feature) => feature.value === true && !!feature.name)
583
+ .map((feature) => feature.name)
584
+ .sort();
585
+ const applicationId = this.sessionService.application?.id ?? '';
586
+ const tenantId = this.sessionService.tenant?.id ?? '';
587
+ return `${tenantId}|${applicationId}|${enabled.join(',')}`;
588
+ }
590
589
  async load() {
591
- if (this.cache) {
592
- return;
593
- }
594
- if (!this.loadPromise) {
595
- this.loadPromise = this.loadDefinitions().finally(() => {
596
- this.loadPromise = null;
590
+ while (true) {
591
+ const availabilityKey = this.buildAvailabilityKey();
592
+ if (this.cache && this.cacheAvailabilityKey === availabilityKey) {
593
+ return;
594
+ }
595
+ if (this.loadPromise) {
596
+ await this.loadPromise;
597
+ continue;
598
+ }
599
+ const keyAtStart = availabilityKey;
600
+ this.cache = null;
601
+ this.cacheAvailabilityKey = null;
602
+ let resolveLoad;
603
+ this.loadPromise = new Promise((resolve) => {
604
+ resolveLoad = resolve;
597
605
  });
606
+ try {
607
+ await this.loadDefinitions();
608
+ if (this.buildAvailabilityKey() === keyAtStart) {
609
+ this.cacheAvailabilityKey = keyAtStart;
610
+ }
611
+ else {
612
+ // Features changed while providers ran. Drop the catalog and rebuild.
613
+ this.cache = null;
614
+ this.cacheAvailabilityKey = null;
615
+ }
616
+ }
617
+ finally {
618
+ this.loadPromise = null;
619
+ resolveLoad();
620
+ }
598
621
  }
599
- await this.loadPromise;
600
622
  }
601
623
  async loadDefinitions() {
602
- if (this.cache) {
603
- return;
604
- }
605
624
  const context = new AXPSettingDefinitionProviderContext();
606
625
  // Load providers from DI tokens — isolate failures so one provider cannot block bootstrap.
607
626
  const providerList = this.normalizeProviders(this.providers);
@@ -629,6 +648,12 @@ class AXPSettingDefinitionProviderService {
629
648
  }
630
649
  async reload() {
631
650
  this.cache = null;
651
+ this.cacheAvailabilityKey = null;
652
+ if (this.loadPromise) {
653
+ await this.loadPromise;
654
+ this.cache = null;
655
+ this.cacheAvailabilityKey = null;
656
+ }
632
657
  await this.load();
633
658
  }
634
659
  async getListAsync(scope) {
@@ -2781,36 +2806,217 @@ class ScopedSettingService {
2781
2806
  }
2782
2807
  }
2783
2808
 
2784
- const DEFAULT_APPLY_DEFAULT_MULTILANGUAGE = true;
2785
- /**
2786
- * Synchronous access to default multi-language behavior for named entity fields and widget
2787
- * property bindings. Mirrors {@link AXPLayoutOrderingConfigService}: avoids async in entity
2788
- * modifiers and startup deadlocks.
2789
- */
2790
- class AXPDefaultMultiLanguageConfigService {
2809
+ //#region ----- Service -----
2810
+ class AXPFilterDefaultOperatorService {
2811
+ //#endregion
2812
+ //#region ----- Lifecycle -----
2791
2813
  constructor() {
2792
- this.settingsService = inject(AXPSettingsService);
2793
- this._applyDefault = signal(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE, /* @ts-ignore */
2794
- ...(ngDevMode ? [{ debugName: "_applyDefault" }] : /* istanbul ignore next */ []));
2795
- this.syncScheduled = false;
2796
- this.settingsService.onLoaded.subscribe(() => this.syncFromSettings());
2797
- this.settingsService.onChanged.subscribe(() => this.syncFromSettings());
2814
+ //#region ----- Services & Dependencies -----
2815
+ this.settings = inject(AXPSettingsService);
2816
+ //#endregion
2817
+ //#region ----- State Signals -----
2818
+ this.overrides = signal({}, /* @ts-ignore */
2819
+ ...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
2820
+ this.refresh();
2821
+ this.settings.onChanged.subscribe(() => {
2822
+ this.refresh();
2823
+ });
2798
2824
  }
2799
- getApplyDefaultMultiLanguage() {
2800
- if (!this.syncScheduled) {
2801
- this.syncScheduled = true;
2802
- this.settingsService
2803
- .get(AXPRegionalSetting.MultiLanguageSupport)
2804
- .then((value) => this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE))
2805
- .catch(() => this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE));
2806
- }
2807
- return this._applyDefault();
2825
+ //#endregion
2826
+ //#region ----- Resolve -----
2827
+ /** Platform override for this filter widget, or `codeDefault` when unset. */
2828
+ resolve(widgetName, codeDefault) {
2829
+ return resolveStoredFilterDefaultOperator(widgetName, this.overrides(), codeDefault);
2808
2830
  }
2809
- async syncFromSettings() {
2810
- try {
2811
- const value = await this.settingsService.get(AXPRegionalSetting.MultiLanguageSupport);
2812
- this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
2813
- }
2831
+ //#endregion
2832
+ //#region ----- Cache -----
2833
+ refresh() {
2834
+ this.overrides.set(readFilterDefaultOperatorMap(this.settings.peekCached(AXPCommonSettings.FilterDefaultOperators)));
2835
+ }
2836
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2837
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, providedIn: 'root' }); }
2838
+ }
2839
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, decorators: [{
2840
+ type: Injectable,
2841
+ args: [{ providedIn: 'root' }]
2842
+ }], ctorParameters: () => [] });
2843
+ //#endregion
2844
+ //#region ----- Widget lookup overlay -----
2845
+ function withFilterDefaultOperator(service, widgetName, widget) {
2846
+ if (!widget?.defaultFilterOperator) {
2847
+ return widget;
2848
+ }
2849
+ const resolved = service.resolve(widgetName, widget.defaultFilterOperator);
2850
+ if (resolved === widget.defaultFilterOperator) {
2851
+ return widget;
2852
+ }
2853
+ return { ...widget, defaultFilterOperator: resolved };
2854
+ }
2855
+
2856
+ const DEFAULT_DATE_FILTER_PRESETS = [
2857
+ { id: 'today', title: '@general:terms.date-presets.today', kind: 'computed' },
2858
+ { id: 'this-week', title: '@general:terms.date-presets.this-week', kind: 'computed' },
2859
+ { id: 'this-month', title: '@general:terms.date-presets.this-month', kind: 'computed' },
2860
+ { id: 'yesterday', title: '@general:terms.date-presets.yesterday', kind: 'computed' },
2861
+ { id: 'last-week', title: '@general:terms.date-presets.last-week', kind: 'computed' },
2862
+ { id: 'last-month', title: '@general:terms.date-presets.last-month', kind: 'computed' },
2863
+ { id: 'tomorrow', title: '@general:terms.date-presets.tomorrow', kind: 'computed' },
2864
+ { id: 'next-week', title: '@general:terms.date-presets.next-week', kind: 'computed' },
2865
+ { id: 'next-month', title: '@general:terms.date-presets.next-month', kind: 'computed' },
2866
+ { id: 'between', title: BETWEEN_OPER.title, kind: 'manual', operationType: 'between' },
2867
+ { id: 'greater-than', title: '@general:terms.operators.greater-than', kind: 'manual', operationType: 'greaterThan' },
2868
+ { id: 'less-than', title: '@general:terms.operators.less-than', kind: 'manual', operationType: 'lessThan' },
2869
+ ];
2870
+ function resolveDateFilterPresets(customPresets, presetIds) {
2871
+ let presets = customPresets?.length ? customPresets : DEFAULT_DATE_FILTER_PRESETS;
2872
+ if (presetIds?.length) {
2873
+ presets = presets.filter((p) => presetIds.includes(p.id));
2874
+ }
2875
+ return presets;
2876
+ }
2877
+ function applyDateFilterPreset(presetId, calendarService) {
2878
+ const currentDate = calendarService.now().date;
2879
+ const calendar = calendarService.calendar;
2880
+ switch (presetId) {
2881
+ case 'today':
2882
+ return {
2883
+ operation: { type: 'between' },
2884
+ value: {
2885
+ from: calendar.startOf(currentDate, 'day').date.toISOString(),
2886
+ to: calendar.endOf(currentDate, 'day').date.toISOString(),
2887
+ },
2888
+ };
2889
+ case 'this-week':
2890
+ return {
2891
+ operation: { type: 'between' },
2892
+ value: {
2893
+ from: calendar.startOf(currentDate, 'week').date.toISOString(),
2894
+ to: calendar.endOf(currentDate, 'week').date.toISOString(),
2895
+ },
2896
+ };
2897
+ case 'this-month':
2898
+ return {
2899
+ operation: { type: 'between' },
2900
+ value: {
2901
+ from: calendar.startOf(currentDate, 'month').date.toISOString(),
2902
+ to: calendar.endOf(currentDate, 'month').date.toISOString(),
2903
+ },
2904
+ };
2905
+ case 'yesterday': {
2906
+ const yesterday = calendar.add(currentDate, 'day', -1);
2907
+ return {
2908
+ operation: { type: 'between' },
2909
+ value: {
2910
+ from: yesterday.startOf('day').date.toISOString(),
2911
+ to: yesterday.endOf('day').date.toISOString(),
2912
+ },
2913
+ };
2914
+ }
2915
+ case 'last-week': {
2916
+ const lastWeek = calendar.add(currentDate, 'week', -1);
2917
+ return {
2918
+ operation: { type: 'between' },
2919
+ value: {
2920
+ from: lastWeek.startOf('week').date.toISOString(),
2921
+ to: lastWeek.endOf('week').date.toISOString(),
2922
+ },
2923
+ };
2924
+ }
2925
+ case 'last-month': {
2926
+ const lastMonth = calendar.add(currentDate, 'month', -1);
2927
+ return {
2928
+ operation: { type: 'between' },
2929
+ value: {
2930
+ from: lastMonth.startOf('month').date.toISOString(),
2931
+ to: lastMonth.endOf('month').date.toISOString(),
2932
+ },
2933
+ };
2934
+ }
2935
+ case 'tomorrow': {
2936
+ const tomorrow = calendar.add(currentDate, 'day', 1);
2937
+ return {
2938
+ operation: { type: 'between' },
2939
+ value: {
2940
+ from: tomorrow.startOf('day').date.toISOString(),
2941
+ to: tomorrow.endOf('day').date.toISOString(),
2942
+ },
2943
+ };
2944
+ }
2945
+ case 'next-week': {
2946
+ const nextWeek = calendar.add(currentDate, 'week', 1);
2947
+ return {
2948
+ operation: { type: 'between' },
2949
+ value: {
2950
+ from: nextWeek.startOf('week').date.toISOString(),
2951
+ to: nextWeek.endOf('week').date.toISOString(),
2952
+ },
2953
+ };
2954
+ }
2955
+ case 'next-month': {
2956
+ const nextMonth = calendar.add(currentDate, 'month', 1);
2957
+ return {
2958
+ operation: { type: 'between' },
2959
+ value: {
2960
+ from: nextMonth.startOf('month').date.toISOString(),
2961
+ to: nextMonth.endOf('month').date.toISOString(),
2962
+ },
2963
+ };
2964
+ }
2965
+ case 'between':
2966
+ return { operation: { type: 'between' } };
2967
+ case 'greater-than':
2968
+ return { operation: { type: 'greaterThan' } };
2969
+ case 'less-than':
2970
+ return { operation: { type: 'lessThan' } };
2971
+ default:
2972
+ return {
2973
+ operation: { type: 'between' },
2974
+ value: { from: '', to: '' },
2975
+ };
2976
+ }
2977
+ }
2978
+ function findManualPresetIdByOperation(operationType, presets) {
2979
+ if (!operationType) {
2980
+ return undefined;
2981
+ }
2982
+ return presets.find((p) => p.kind === 'manual' && p.operationType === operationType)?.id;
2983
+ }
2984
+
2985
+ const AXP_HOME_PAGES = new InjectionToken('AXP_HOME_PAGES');
2986
+ const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY', {
2987
+ factory: () => 'home'
2988
+ });
2989
+
2990
+ const DEFAULT_APPLY_DEFAULT_MULTILANGUAGE = true;
2991
+ /**
2992
+ * Synchronous access to default multi-language behavior for named entity fields and widget
2993
+ * property bindings. Mirrors {@link AXPLayoutOrderingConfigService}: avoids async in entity
2994
+ * modifiers and startup deadlocks.
2995
+ */
2996
+ class AXPDefaultMultiLanguageConfigService {
2997
+ constructor() {
2998
+ this.settingsService = inject(AXPSettingsService);
2999
+ this._applyDefault = signal(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE, /* @ts-ignore */
3000
+ ...(ngDevMode ? [{ debugName: "_applyDefault" }] : /* istanbul ignore next */ []));
3001
+ this.syncScheduled = false;
3002
+ this.settingsService.onLoaded.subscribe(() => this.syncFromSettings());
3003
+ this.settingsService.onChanged.subscribe(() => this.syncFromSettings());
3004
+ }
3005
+ getApplyDefaultMultiLanguage() {
3006
+ if (!this.syncScheduled) {
3007
+ this.syncScheduled = true;
3008
+ this.settingsService
3009
+ .get(AXPRegionalSetting.MultiLanguageSupport)
3010
+ .then((value) => this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE))
3011
+ .catch(() => this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE));
3012
+ }
3013
+ return this._applyDefault();
3014
+ }
3015
+ async syncFromSettings() {
3016
+ try {
3017
+ const value = await this.settingsService.get(AXPRegionalSetting.MultiLanguageSupport);
3018
+ this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
3019
+ }
2814
3020
  catch {
2815
3021
  this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
2816
3022
  }
@@ -2977,6 +3183,52 @@ function withDefaultMultiLanguageOnWidgetProperty(prop, applyDefault) {
2977
3183
  }
2978
3184
  //#endregion
2979
3185
 
3186
+ //#region ----- User status feature -----
3187
+ /**
3188
+ * Platform switch for showing user availability.
3189
+ * Stays on until {@link AXPCommonSettings.UserStatusEnabled} is false.
3190
+ */
3191
+ class AXPUserStatusFeatureService {
3192
+ //#endregion
3193
+ //#region ----- Lifecycle -----
3194
+ constructor() {
3195
+ //#region ----- Services & Dependencies -----
3196
+ this.settingsService = inject(AXPSettingsService);
3197
+ //#endregion
3198
+ //#region ----- State Signals -----
3199
+ this.enabledSignal = signal(AXP_USER_STATUS_ENABLED_DEFAULT, /* @ts-ignore */
3200
+ ...(ngDevMode ? [{ debugName: "enabledSignal" }] : /* istanbul ignore next */ []));
3201
+ /** Live platform flag. True until settings say otherwise. */
3202
+ this.enabled = this.enabledSignal.asReadonly();
3203
+ this.settingsService.onLoaded.subscribe(() => {
3204
+ void this.refresh();
3205
+ });
3206
+ this.settingsService.onChanged.subscribe((event) => {
3207
+ if (!event.keys.includes(AXPCommonSettings.UserStatusEnabled)) {
3208
+ return;
3209
+ }
3210
+ void this.refresh();
3211
+ });
3212
+ }
3213
+ //#endregion
3214
+ //#region ----- Public API -----
3215
+ async isEnabled() {
3216
+ return this.refresh();
3217
+ }
3218
+ async refresh() {
3219
+ const value = await this.settingsService.get(AXPCommonSettings.UserStatusEnabled);
3220
+ const enabled = value !== false;
3221
+ this.enabledSignal.set(enabled);
3222
+ return enabled;
3223
+ }
3224
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3225
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, providedIn: 'root' }); }
3226
+ }
3227
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, decorators: [{
3228
+ type: Injectable,
3229
+ args: [{ providedIn: 'root' }]
3230
+ }], ctorParameters: () => [] });
3231
+
2980
3232
  //#region ----- Platform settings load state -----
2981
3233
  /**
2982
3234
  * Tracks fatal failure / offline-continue of the public platform (P) settings bootstrap.
@@ -3382,65 +3634,273 @@ class AXPUnsavedChangesPopstateService {
3382
3634
  }
3383
3635
  });
3384
3636
  }
3385
- //#endregion
3386
- //#region ---- Registry ----
3387
- registerSource(source) {
3388
- this.sources = this.sources.filter((entry) => entry.id !== source.id);
3389
- this.sources.push(source);
3637
+ //#endregion
3638
+ //#region ---- Registry ----
3639
+ registerSource(source) {
3640
+ this.sources = this.sources.filter((entry) => entry.id !== source.id);
3641
+ this.sources.push(source);
3642
+ }
3643
+ unregisterSource(id) {
3644
+ this.sources = this.sources.filter((entry) => entry.id !== id);
3645
+ }
3646
+ //#endregion
3647
+ //#region ---- Popstate Handler ----
3648
+ /** True while a popstate back/forward is being intercepted or confirmed. */
3649
+ isHandlingPopstate() {
3650
+ return this.handling;
3651
+ }
3652
+ async handlePopstateNavigation(start) {
3653
+ if (this.handling) {
3654
+ return;
3655
+ }
3656
+ if (this.confirm.hasLeaveConfirmed()) {
3657
+ return;
3658
+ }
3659
+ const dirty = this.isDirty();
3660
+ if (!dirty) {
3661
+ return;
3662
+ }
3663
+ const stayUrl = this.committedUrl;
3664
+ const targetUrl = start.url;
3665
+ if (!stayUrl || stayUrl === targetUrl) {
3666
+ return;
3667
+ }
3668
+ this.handling = true;
3669
+ try {
3670
+ this.location.go(stayUrl);
3671
+ const allowed = await this.confirm.confirmIfDirty(true);
3672
+ if (!allowed) {
3673
+ await this.router.navigateByUrl(stayUrl, { replaceUrl: true });
3674
+ return;
3675
+ }
3676
+ window.history.back();
3677
+ }
3678
+ finally {
3679
+ this.handling = false;
3680
+ }
3681
+ }
3682
+ //#endregion
3683
+ //#region ---- Utility Methods ----
3684
+ isDirty() {
3685
+ return this.sources.some((source) => source.isDirty());
3686
+ }
3687
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3688
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, providedIn: 'root' }); }
3689
+ }
3690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, decorators: [{
3691
+ type: Injectable,
3692
+ args: [{
3693
+ providedIn: 'root',
3694
+ }]
3695
+ }], ctorParameters: () => [] });
3696
+
3697
+ //#endregion
3698
+ //#region ---- App Path Navigation ----
3699
+ /**
3700
+ * Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
3701
+ * Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
3702
+ */
3703
+ function axpParseAppPath(path) {
3704
+ const trimmed = path.trim();
3705
+ const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
3706
+ const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
3707
+ const [pathPart, fragment] = pathAndFragment.split('#');
3708
+ const segments = pathPart.split('/').filter(Boolean);
3709
+ const queryParams = queryString
3710
+ ? queryString.split('&').reduce((params, pair) => {
3711
+ const [key, value = ''] = pair.split('=');
3712
+ if (key) {
3713
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
3714
+ }
3715
+ return params;
3716
+ }, {})
3717
+ : undefined;
3718
+ return {
3719
+ segments,
3720
+ queryParams,
3721
+ fragment: fragment || undefined,
3722
+ };
3723
+ }
3724
+ /** Navigates using explicit path segments (same pattern as entity preload filters). */
3725
+ function axpNavigateAppPath(router, path, extras) {
3726
+ const { segments, queryParams, fragment } = axpParseAppPath(path);
3727
+ return router.navigate(segments, {
3728
+ ...extras,
3729
+ queryParams,
3730
+ fragment,
3731
+ });
3732
+ }
3733
+ /** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
3734
+ function axpIsEntityListPath(url) {
3735
+ const path = url.split('?')[0].split('#')[0];
3736
+ return /\/e\/[^/]+\/list$/.test(path);
3737
+ }
3738
+ /** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
3739
+ function axpIsEntityListRoute(router) {
3740
+ return axpIsEntityListPath(router.url);
3741
+ }
3742
+ /** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
3743
+ function axpIsEntityDetailsPath(url) {
3744
+ const path = url.split('?')[0].split('#')[0];
3745
+ return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
3746
+ }
3747
+ /**
3748
+ * Entity key (`module.entity`) for `/{app}/m/{module}/e/{entity}/{id}/view`.
3749
+ * Query and hash are ignored. Returns null for every other path.
3750
+ */
3751
+ function axpEntityKeyFromDetailsPath(url) {
3752
+ const path = url.split('?')[0]?.split('#')[0] ?? '';
3753
+ const match = /\/m\/([^/]+)\/e\/([^/]+)\/[^/]+\/view$/.exec(path);
3754
+ const moduleName = match?.[1];
3755
+ const entityName = match?.[2];
3756
+ if (!moduleName || !entityName) {
3757
+ return null;
3758
+ }
3759
+ return `${moduleName}.${entityName}`;
3760
+ }
3761
+ /** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
3762
+ function axpIsEntityDetailsViewRoute(router) {
3763
+ return axpIsEntityDetailsPath(router.url);
3764
+ }
3765
+ /**
3766
+ * Merges query params on the current entity list route without pushing history.
3767
+ * Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
3768
+ */
3769
+ function axpReplaceEntityListRouteQueryParams(router, queryParams) {
3770
+ const url = router.url;
3771
+ if (!axpIsEntityListPath(url)) {
3772
+ return;
3773
+ }
3774
+ const parsed = router.parseUrl(url);
3775
+ const pathOnly = url.split('?')[0].split('#')[0];
3776
+ const { segments } = axpParseAppPath(pathOnly);
3777
+ const mergedQueryParams = { ...parsed.queryParams, ...queryParams };
3778
+ void router.navigate(segments, {
3779
+ queryParams: mergedQueryParams,
3780
+ replaceUrl: true,
3781
+ });
3782
+ }
3783
+ //#endregion
3784
+
3785
+ //#region ----- Constants -----
3786
+ /** Query param that distinguishes two menus pointing at the same entity list. */
3787
+ const ENTITY_LIST_INSTANCE_QUERY_PARAM = 'list';
3788
+ /** Used when the list is opened without a menu (command, deep link). */
3789
+ const ENTITY_LIST_DEFAULT_INSTANCE_ID = 'default';
3790
+ //#endregion
3791
+ //#region ----- Instance id -----
3792
+ function axpNormalizeEntityListInstanceId(value) {
3793
+ if (typeof value !== 'string') {
3794
+ return ENTITY_LIST_DEFAULT_INSTANCE_ID;
3795
+ }
3796
+ const trimmed = value.trim();
3797
+ return trimmed.length > 0 ? trimmed : ENTITY_LIST_DEFAULT_INSTANCE_ID;
3798
+ }
3799
+ function axpGetEntityListInstanceIdFromQueryString(queryString) {
3800
+ const params = new URLSearchParams(queryString.startsWith('?') ? queryString.slice(1) : queryString);
3801
+ return axpNormalizeEntityListInstanceId(params.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
3802
+ }
3803
+ function axpGetEntityListInstanceIdFromParamMap(queryParamMap) {
3804
+ return axpNormalizeEntityListInstanceId(queryParamMap.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
3805
+ }
3806
+ function axpGetEntityListInstanceIdFromPath(path) {
3807
+ const queryString = path.split('#')[0].split('?')[1] ?? '';
3808
+ return axpGetEntityListInstanceIdFromQueryString(queryString);
3809
+ }
3810
+ //#endregion
3811
+ //#region ----- Menu / URL stamping -----
3812
+ /**
3813
+ * Appends `list={instanceId}` to an entity-list path when missing.
3814
+ * Named menu entries get a stable identity so Angular reuses the list route
3815
+ * while filter persistence can still isolate each menu.
3816
+ */
3817
+ function axpEnsureEntityListInstanceQueryParam(path, instanceId) {
3818
+ if (!path || !instanceId || !axpIsEntityListPath(path)) {
3819
+ return path;
3820
+ }
3821
+ const normalized = axpNormalizeEntityListInstanceId(instanceId);
3822
+ if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
3823
+ return path;
3390
3824
  }
3391
- unregisterSource(id) {
3392
- this.sources = this.sources.filter((entry) => entry.id !== id);
3825
+ const { segments, queryParams, fragment } = axpParseAppPath(path);
3826
+ if (queryParams?.[ENTITY_LIST_INSTANCE_QUERY_PARAM]) {
3827
+ return path;
3393
3828
  }
3394
- //#endregion
3395
- //#region ---- Popstate Handler ----
3396
- /** True while a popstate back/forward is being intercepted or confirmed. */
3397
- isHandlingPopstate() {
3398
- return this.handling;
3829
+ const nextQuery = new URLSearchParams({
3830
+ ...(queryParams ?? {}),
3831
+ [ENTITY_LIST_INSTANCE_QUERY_PARAM]: normalized,
3832
+ });
3833
+ const base = `/${segments.join('/')}`;
3834
+ const withQuery = `${base}?${nextQuery.toString()}`;
3835
+ return fragment ? `${withQuery}#${fragment}` : withQuery;
3836
+ }
3837
+ /**
3838
+ * Query keys the list page rewrites while the user stays on the same menu
3839
+ * (`view`, `filters`, layout, refresh). They must not open another tab.
3840
+ */
3841
+ const ENTITY_LIST_PAGE_STATE_QUERY_KEYS = new Set(['view', 'layout', 'filters', '_ts']);
3842
+ function pushEntityListNavigationPair(pairs, key, raw) {
3843
+ if (!key || ENTITY_LIST_PAGE_STATE_QUERY_KEYS.has(key)) {
3844
+ return;
3399
3845
  }
3400
- async handlePopstateNavigation(start) {
3401
- if (this.handling) {
3402
- return;
3403
- }
3404
- if (this.confirm.hasLeaveConfirmed()) {
3405
- return;
3406
- }
3407
- const dirty = this.isDirty();
3408
- if (!dirty) {
3409
- return;
3410
- }
3411
- const stayUrl = this.committedUrl;
3412
- const targetUrl = start.url;
3413
- if (!stayUrl || stayUrl === targetUrl) {
3846
+ const value = Array.isArray(raw) ? raw[0] : raw;
3847
+ if (typeof value !== 'string') {
3848
+ return;
3849
+ }
3850
+ if (key === ENTITY_LIST_INSTANCE_QUERY_PARAM) {
3851
+ const normalized = axpNormalizeEntityListInstanceId(value);
3852
+ if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
3414
3853
  return;
3415
3854
  }
3416
- this.handling = true;
3417
- try {
3418
- this.location.go(stayUrl);
3419
- const allowed = await this.confirm.confirmIfDirty(true);
3420
- if (!allowed) {
3421
- await this.router.navigateByUrl(stayUrl, { replaceUrl: true });
3422
- return;
3423
- }
3424
- window.history.back();
3425
- }
3426
- finally {
3427
- this.handling = false;
3428
- }
3855
+ pairs.push([key, normalized]);
3856
+ return;
3429
3857
  }
3430
- //#endregion
3431
- //#region ---- Utility Methods ----
3432
- isDirty() {
3433
- return this.sources.some((source) => source.isDirty());
3858
+ pairs.push([key, value]);
3859
+ }
3860
+ function readEntityListNavigationPairs(query) {
3861
+ const pairs = [];
3862
+ if (!query) {
3863
+ return pairs;
3434
3864
  }
3435
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3436
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, providedIn: 'root' }); }
3865
+ if (typeof query === 'string') {
3866
+ const hashless = query.split('#')[0];
3867
+ const queryMarker = hashless.indexOf('?');
3868
+ // A path with no "?" is not a query. Parsing it makes the path itself a key and opens a second tab.
3869
+ const queryString = queryMarker >= 0 ? hashless.slice(queryMarker + 1) : hashless.startsWith('/') ? '' : hashless;
3870
+ const params = new URLSearchParams(queryString);
3871
+ params.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
3872
+ return pairs;
3873
+ }
3874
+ if (typeof URLSearchParams !== 'undefined' && query instanceof URLSearchParams) {
3875
+ query.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
3876
+ return pairs;
3877
+ }
3878
+ for (const [key, value] of Object.entries(query)) {
3879
+ pushEntityListNavigationPair(pairs, key, value);
3880
+ }
3881
+ return pairs;
3437
3882
  }
3438
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, decorators: [{
3439
- type: Injectable,
3440
- args: [{
3441
- providedIn: 'root',
3442
- }]
3443
- }], ctorParameters: () => [] });
3883
+ /**
3884
+ * Tab and route-cache identity for an entity list.
3885
+ * Navigation query params (`mode=cm`, `mode=pm`, non-default `list`, …) keep each menu distinct,
3886
+ * the same way simple navigation does. Page-state params stay on the URL and do not split the tab.
3887
+ */
3888
+ function axpEntityListNavigationTabKey(path, query) {
3889
+ const base = path.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
3890
+ if (!axpIsEntityListPath(base)) {
3891
+ return base;
3892
+ }
3893
+ const pairs = readEntityListNavigationPairs(query);
3894
+ if (!pairs.length) {
3895
+ return base;
3896
+ }
3897
+ pairs.sort((left, right) => left[0].localeCompare(right[0]) || left[1].localeCompare(right[1]));
3898
+ const queryString = pairs
3899
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
3900
+ .join('&');
3901
+ return `${base}?${queryString}`;
3902
+ }
3903
+ //#endregion
3444
3904
 
3445
3905
  //#endregion
3446
3906
  /**
@@ -3458,6 +3918,10 @@ class AXPTabRouteCacheRegistry {
3458
3918
  this.openKeys = new Set();
3459
3919
  this.enabled = false;
3460
3920
  this.deactivateGuardSuspended = false;
3921
+ /** While true, cached pages stay stored but must not be attached to a navigation. */
3922
+ this.attachSuspended = false;
3923
+ /** Session that owns cached pages. Null means signed out: never keep another user's components. */
3924
+ this.ownerKey = null;
3461
3925
  }
3462
3926
  //#endregion
3463
3927
  //#region ---- Deactivate Guard Suspension ----
@@ -3481,6 +3945,35 @@ class AXPTabRouteCacheRegistry {
3481
3945
  }
3482
3946
  //#endregion
3483
3947
  //#region ---- Mode ----
3948
+ /** True while a signed-in session may keep detached pages. */
3949
+ canCache() {
3950
+ return this.ownerKey !== null;
3951
+ }
3952
+ /** True while a session transition must not reattach a cached page. */
3953
+ isAttachSuspended() {
3954
+ return this.attachSuspended;
3955
+ }
3956
+ /**
3957
+ * Keeps cached pages for a failed switch, but blocks attaching them until the transition settles.
3958
+ */
3959
+ suspendAttach() {
3960
+ this.attachSuspended = true;
3961
+ }
3962
+ /** Allows attach again. Does not destroy the stored pages. */
3963
+ resumeAttach() {
3964
+ this.attachSuspended = false;
3965
+ }
3966
+ /**
3967
+ * Binds cached pages to one session. A sign-out or a different user destroys every cached
3968
+ * component so the next session cannot reattach the previous user's data.
3969
+ */
3970
+ bindOwner(ownerKey) {
3971
+ if (this.ownerKey === ownerKey) {
3972
+ return;
3973
+ }
3974
+ this.ownerKey = ownerKey;
3975
+ this.clear();
3976
+ }
3484
3977
  /** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
3485
3978
  setEnabled(enabled) {
3486
3979
  if (this.enabled === enabled) {
@@ -3502,7 +3995,11 @@ class AXPTabRouteCacheRegistry {
3502
3995
  */
3503
3996
  syncOpenKeys(scopedKeys, activeTabUrls = []) {
3504
3997
  const scopedSet = new Set(scopedKeys);
3505
- const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
3998
+ const activePathSet = new Set(activeTabUrls.flatMap((url) => {
3999
+ const pathOnly = url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
4000
+ const withNavigation = axpEntityListNavigationTabKey(pathOnly, url);
4001
+ return withNavigation === pathOnly ? [pathOnly] : [pathOnly, withNavigation];
4002
+ }));
3506
4003
  for (const cacheKey of [...this.handles.keys()]) {
3507
4004
  const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
3508
4005
  const keepsScopedKey = scopedSet.has(normalizedCacheKey);
@@ -3544,6 +4041,10 @@ class AXPTabRouteCacheRegistry {
3544
4041
  * destroying the old one would destroy the live component and corrupt the view (NG0922).
3545
4042
  */
3546
4043
  store(key, handle) {
4044
+ if (!this.ownerKey) {
4045
+ this.destroyHandle(handle);
4046
+ return;
4047
+ }
3547
4048
  this.handles.set(key, handle);
3548
4049
  }
3549
4050
  /**
@@ -3553,6 +4054,9 @@ class AXPTabRouteCacheRegistry {
3553
4054
  * `store(snapshot, null)`.
3554
4055
  */
3555
4056
  retrieve(key) {
4057
+ if (this.attachSuspended) {
4058
+ return null;
4059
+ }
3556
4060
  return this.handles.get(key) ?? null;
3557
4061
  }
3558
4062
  /**
@@ -3832,6 +4336,7 @@ const AXP_EXPERIENCE_SETTINGS_SECTIONS = {
3832
4336
  Performance: 'performance',
3833
4337
  Entity: 'entity',
3834
4338
  Offline: 'offline',
4339
+ Presence: 'presence',
3835
4340
  FileChooser: 'file-chooser',
3836
4341
  };
3837
4342
  //#endregion
@@ -5329,7 +5834,7 @@ class AXPBackgroundOperationSlotComponent {
5329
5834
  this.popover()?.close();
5330
5835
  }
5331
5836
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5332
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPBackgroundOperationSlotComponent, isStandalone: true, selector: "axp-background-operation-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (showSlot()) {\n <!-- ----- Footer tray trigger ----- -->\n <button type=\"button\" class=\"operation-slot__trigger\" #operationTrigger (click)=\"onTriggerClick(panel)\">\n @if (badgeDisplay()) {\n <!-- ----- Count badge ----- -->\n <ax-badge class=\"operation-slot__badge\" [class.--xs]=\"badgeDisplay().length > 2\" color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n @if (slotShowsFailureOnly()) {\n <!-- ----- Failed tasks ----- -->\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-circle-exclamation\"></i>\n </ax-icon>\n }\n <!--->\n <!-- ----- In-progress spinner (no icon for completed) ----- -->\n @else if (!slotShowsRetainedOnly()) {\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-spinner fa-spin\"></i>\n </ax-icon>\n }\n <span class=\"operation-slot__text\">\n {{ slotLabelKey() | translate | async }}\n </span>\n </button>\n\n <ax-popover\n [target]=\"operationTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n (onOpened)=\"onPopoverOpened(panel)\"\n >\n <axp-background-operation-panel\n #panel\n (onClosePopover)=\"closePopover()\"\n ></axp-background-operation-panel>\n </ax-popover>\n}\n", styles: ["@layer properties;axp-background-operation-slot{display:inline-flex}.operation-slot__trigger{display:inline-flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:var(--spacing, .25rem);color:var(--color-text-default);--ax-sys-size: ax-sm;--ax-sys-size-base: calc(var(--ax-sys-size-md) - var(--ax-sys-size-step));--spacing: calc(var(--ax-sys-spacing-base) - .25 * var(--ax-sys-padding-step));--ax-sys-border-radius: var(--ax-sys-border-radius-base);--text-xs: calc(var(--ax-sys-text-xs-base) * 11 / 12);--text-sm: calc(var(--ax-sys-text-sm-base) * 6 / 7);--text-base: calc(var(--ax-sys-body-font-size) * .875);--text-lg: calc(var(--ax-sys-text-lg-base) * 8 / 9);--text-xl: calc(var(--ax-sys-text-xl-base) * .9);--leading-3: calc(var(--ax-sys-leading-3-base) * 7 / 6);--leading-4: var(--ax-sys-leading-4-base);--leading-5: calc(var(--ax-sys-leading-5-base) * .9);--leading-6: calc(var(--ax-sys-leading-6-base) * 11 / 12)}@media(hover:hover){.operation-slot__trigger:hover{background-color:rgba(var(--ax-sys-color-light-surface))}}.operation-slot__icon{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.operation-slot__text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.operation-slot__badge{flex-shrink:0}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i3$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "closeOnNavigation", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPBackgroundOperationPanelComponent, selector: "axp-background-operation-panel", outputs: ["onActiveCountChange", "onClosePopover"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
5837
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPBackgroundOperationSlotComponent, isStandalone: true, selector: "axp-background-operation-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (showSlot()) {\n <!-- ----- Footer tray trigger ----- -->\n <button type=\"button\" class=\"operation-slot__trigger\" #operationTrigger (click)=\"onTriggerClick(panel)\">\n @if (badgeDisplay()) {\n <!-- ----- Count badge ----- -->\n <ax-badge class=\"operation-slot__badge\" [class.--xs]=\"badgeDisplay().length > 2\" color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n @if (slotShowsFailureOnly()) {\n <!-- ----- Failed tasks ----- -->\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-circle-exclamation\"></i>\n </ax-icon>\n }\n <!--->\n <!-- ----- In-progress spinner (no icon for completed) ----- -->\n @else if (!slotShowsRetainedOnly()) {\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-spinner fa-spin\"></i>\n </ax-icon>\n }\n <span class=\"operation-slot__text\">\n {{ slotLabelKey() | translate | async }}\n </span>\n </button>\n\n <ax-popover\n [target]=\"operationTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n (onOpened)=\"onPopoverOpened(panel)\"\n >\n <axp-background-operation-panel\n #panel\n (onClosePopover)=\"closePopover()\"\n ></axp-background-operation-panel>\n </ax-popover>\n}\n", styles: ["@layer properties;axp-background-operation-slot{display:inline-flex}.operation-slot__trigger{display:inline-flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:var(--spacing, .25rem);color:var(--color-text-default);--ax-sys-size: ax-sm;--ax-sys-size-base: calc(var(--ax-sys-size-md) - var(--ax-sys-size-step));--spacing: calc(var(--ax-sys-spacing-base) - .25 * var(--ax-sys-padding-step));--ax-sys-border-radius: var(--ax-sys-border-radius-base);--text-xs: calc(var(--ax-sys-text-xs-base) * 11 / 12);--text-sm: calc(var(--ax-sys-text-sm-base) * 6 / 7);--text-base: calc(var(--ax-sys-body-font-size) * .875);--text-lg: calc(var(--ax-sys-text-lg-base) * 8 / 9);--text-xl: calc(var(--ax-sys-text-xl-base) * .9);--leading-3: calc(var(--ax-sys-leading-3-base) * 7 / 6);--leading-4: var(--ax-sys-leading-4-base);--leading-5: calc(var(--ax-sys-leading-5-base) * .9);--leading-6: calc(var(--ax-sys-leading-6-base) * 11 / 12)}@media(hover:hover){.operation-slot__trigger:hover{background-color:rgba(var(--ax-sys-color-light-surface))}}.operation-slot__icon{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.operation-slot__text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.operation-slot__badge{flex-shrink:0}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i3$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "restoreFocusOnClose", "openAfter", "closeAfter", "closeOnScroll", "closeOnNavigation", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPBackgroundOperationPanelComponent, selector: "axp-background-operation-panel", outputs: ["onActiveCountChange", "onClosePopover"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
5333
5838
  }
5334
5839
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, decorators: [{
5335
5840
  type: Component,
@@ -5600,148 +6105,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
5600
6105
  args: [{ providedIn: 'root' }]
5601
6106
  }] });
5602
6107
 
5603
- //#endregion
5604
- //#region ---- App Path Navigation ----
5605
- /**
5606
- * Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
5607
- * Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
5608
- */
5609
- function axpParseAppPath(path) {
5610
- const trimmed = path.trim();
5611
- const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
5612
- const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
5613
- const [pathPart, fragment] = pathAndFragment.split('#');
5614
- const segments = pathPart.split('/').filter(Boolean);
5615
- const queryParams = queryString
5616
- ? queryString.split('&').reduce((params, pair) => {
5617
- const [key, value = ''] = pair.split('=');
5618
- if (key) {
5619
- params[decodeURIComponent(key)] = decodeURIComponent(value);
5620
- }
5621
- return params;
5622
- }, {})
5623
- : undefined;
5624
- return {
5625
- segments,
5626
- queryParams,
5627
- fragment: fragment || undefined,
5628
- };
5629
- }
5630
- /** Navigates using explicit path segments (same pattern as entity preload filters). */
5631
- function axpNavigateAppPath(router, path, extras) {
5632
- const { segments, queryParams, fragment } = axpParseAppPath(path);
5633
- return router.navigate(segments, {
5634
- ...extras,
5635
- queryParams,
5636
- fragment,
5637
- });
5638
- }
5639
- /** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
5640
- function axpIsEntityListPath(url) {
5641
- const path = url.split('?')[0].split('#')[0];
5642
- return /\/e\/[^/]+\/list$/.test(path);
5643
- }
5644
- /** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
5645
- function axpIsEntityListRoute(router) {
5646
- return axpIsEntityListPath(router.url);
5647
- }
5648
- /** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
5649
- function axpIsEntityDetailsPath(url) {
5650
- const path = url.split('?')[0].split('#')[0];
5651
- return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
5652
- }
5653
- /**
5654
- * Entity key (`module.entity`) for `/{app}/m/{module}/e/{entity}/{id}/view`.
5655
- * Query and hash are ignored. Returns null for every other path.
5656
- */
5657
- function axpEntityKeyFromDetailsPath(url) {
5658
- const path = url.split('?')[0]?.split('#')[0] ?? '';
5659
- const match = /\/m\/([^/]+)\/e\/([^/]+)\/[^/]+\/view$/.exec(path);
5660
- const moduleName = match?.[1];
5661
- const entityName = match?.[2];
5662
- if (!moduleName || !entityName) {
5663
- return null;
5664
- }
5665
- return `${moduleName}.${entityName}`;
5666
- }
5667
- /** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
5668
- function axpIsEntityDetailsViewRoute(router) {
5669
- return axpIsEntityDetailsPath(router.url);
5670
- }
5671
- /**
5672
- * Merges query params on the current entity list route without pushing history.
5673
- * Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
5674
- */
5675
- function axpReplaceEntityListRouteQueryParams(router, queryParams) {
5676
- const url = router.url;
5677
- if (!axpIsEntityListPath(url)) {
5678
- return;
5679
- }
5680
- const parsed = router.parseUrl(url);
5681
- const pathOnly = url.split('?')[0].split('#')[0];
5682
- const { segments } = axpParseAppPath(pathOnly);
5683
- const mergedQueryParams = { ...parsed.queryParams, ...queryParams };
5684
- void router.navigate(segments, {
5685
- queryParams: mergedQueryParams,
5686
- replaceUrl: true,
5687
- });
5688
- }
5689
- //#endregion
5690
-
5691
- //#region ----- Constants -----
5692
- /** Query param that distinguishes two menus pointing at the same entity list. */
5693
- const ENTITY_LIST_INSTANCE_QUERY_PARAM = 'list';
5694
- /** Used when the list is opened without a menu (command, deep link). */
5695
- const ENTITY_LIST_DEFAULT_INSTANCE_ID = 'default';
5696
- //#endregion
5697
- //#region ----- Instance id -----
5698
- function axpNormalizeEntityListInstanceId(value) {
5699
- if (typeof value !== 'string') {
5700
- return ENTITY_LIST_DEFAULT_INSTANCE_ID;
5701
- }
5702
- const trimmed = value.trim();
5703
- return trimmed.length > 0 ? trimmed : ENTITY_LIST_DEFAULT_INSTANCE_ID;
5704
- }
5705
- function axpGetEntityListInstanceIdFromQueryString(queryString) {
5706
- const params = new URLSearchParams(queryString.startsWith('?') ? queryString.slice(1) : queryString);
5707
- return axpNormalizeEntityListInstanceId(params.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
5708
- }
5709
- function axpGetEntityListInstanceIdFromParamMap(queryParamMap) {
5710
- return axpNormalizeEntityListInstanceId(queryParamMap.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
5711
- }
5712
- function axpGetEntityListInstanceIdFromPath(path) {
5713
- const queryString = path.split('#')[0].split('?')[1] ?? '';
5714
- return axpGetEntityListInstanceIdFromQueryString(queryString);
5715
- }
5716
- //#endregion
5717
- //#region ----- Menu / URL stamping -----
5718
- /**
5719
- * Appends `list={instanceId}` to an entity-list path when missing.
5720
- * Named menu entries get a stable identity so Angular reuses the list route
5721
- * while filter persistence can still isolate each menu.
5722
- */
5723
- function axpEnsureEntityListInstanceQueryParam(path, instanceId) {
5724
- if (!path || !instanceId || !axpIsEntityListPath(path)) {
5725
- return path;
5726
- }
5727
- const normalized = axpNormalizeEntityListInstanceId(instanceId);
5728
- if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
5729
- return path;
5730
- }
5731
- const { segments, queryParams, fragment } = axpParseAppPath(path);
5732
- if (queryParams?.[ENTITY_LIST_INSTANCE_QUERY_PARAM]) {
5733
- return path;
5734
- }
5735
- const nextQuery = new URLSearchParams({
5736
- ...(queryParams ?? {}),
5737
- [ENTITY_LIST_INSTANCE_QUERY_PARAM]: normalized,
5738
- });
5739
- const base = `/${segments.join('/')}`;
5740
- const withQuery = `${base}?${nextQuery.toString()}`;
5741
- return fragment ? `${withQuery}#${fragment}` : withQuery;
5742
- }
5743
- //#endregion
5744
-
5745
6108
  //#region ----- Menu instance identity -----
5746
6109
  /** Prefers the stamped `list` query param, then the menu item name. */
5747
6110
  function axpMenuItemListInstanceId(item) {
@@ -7051,6 +7414,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7051
7414
  function axpMenuItemNeedsUpdate(item, updates) {
7052
7415
  return Object.keys(updates).some((key) => item[key] !== updates[key]);
7053
7416
  }
7417
+ /** True when the menu command itself navigates. A path on the same item must not navigate again. */
7418
+ function axpMenuCommandIsNavigation(command) {
7419
+ if (!command || typeof command !== 'object') {
7420
+ return false;
7421
+ }
7422
+ if ('type' in command && command.type === 'router') {
7423
+ return true;
7424
+ }
7425
+ return 'name' in command && command.name === 'navigate';
7426
+ }
7054
7427
  /** Immutably patches the first menu item with the given name. */
7055
7428
  function axpUpdateMenuItemByName(items, name, updates) {
7056
7429
  let changed = false;
@@ -7124,18 +7497,22 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
7124
7497
  });
7125
7498
  },
7126
7499
  executeCommand(item) {
7127
- if (item.path) {
7128
- const path = item.name
7500
+ const path = item.path
7501
+ ? item.name
7129
7502
  ? axpEnsureEntityListInstanceQueryParam(item.path, item.name)
7130
- : item.path;
7503
+ : item.path
7504
+ : undefined;
7505
+ if (path) {
7131
7506
  void commandService.execute('navigate', { path });
7132
7507
  patchState(store, { selectedMenuItem: { item, isFullMatch: true } });
7133
7508
  }
7134
- if (item.command) {
7135
- const command = item.command;
7136
- if (commandService.exists(command?.name)) {
7137
- void commandService.execute(command.name, command.options ?? {});
7138
- }
7509
+ const command = item.command;
7510
+ if (!command || (path && axpMenuCommandIsNavigation(command))) {
7511
+ return;
7512
+ }
7513
+ const executeCommand = command;
7514
+ if (executeCommand.name && commandService.exists(executeCommand.name)) {
7515
+ void commandService.execute(executeCommand.name, executeCommand.options ?? {});
7139
7516
  }
7140
7517
  },
7141
7518
  isItemOpen(item) {
@@ -7248,13 +7625,14 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
7248
7625
  const IGNORED_QUERY_KEYS = new Set(['_ts']);
7249
7626
  /**
7250
7627
  * Builds a stable tab key from a router URL.
7251
- * Path segments define identity; query params are excluded so the same entity record
7252
- * or list reuses one tab even when filters or refresh tokens change.
7628
+ * Path segments define the page. Entity-list navigation query params (`mode`, `list`, …)
7629
+ * stay in the key so two menus of the same entity stay distinct, matching simple navigation.
7630
+ * Page-state params (`view`, `filters`, `_ts`) stay on the URL and do not open another tab.
7253
7631
  */
7254
7632
  function axpBuildTabKey(url) {
7255
7633
  const normalized = normalizeRouterUrl(url);
7256
7634
  const pathOnly = normalized.split('?')[0].split('#')[0];
7257
- return pathOnly;
7635
+ return axpEntityListNavigationTabKey(pathOnly, normalized);
7258
7636
  }
7259
7637
  /** Normalizes a router URL for storage and navigation. */
7260
7638
  function normalizeRouterUrl(url) {
@@ -7299,6 +7677,73 @@ function axpFallbackTabTitle(url) {
7299
7677
  }
7300
7678
  //#endregion
7301
7679
 
7680
+ //#region ---- Constants ----
7681
+ /** Query param that keeps the failed route as the tab identity. */
7682
+ const AXP_IN_TAB_ERROR_FROM_PARAM = 'from';
7683
+ const IN_TAB_ERROR_PATHS = new Set(['/error/401', '/error/404', '/error/offline', '/401', '/404']);
7684
+ //#endregion
7685
+ //#region ---- URL Helpers ----
7686
+ /**
7687
+ * Original page URL carried on an in-shell error route.
7688
+ * Null when the URL is not an error page or `from` is missing.
7689
+ */
7690
+ function axpInTabErrorSourceUrl(url) {
7691
+ const normalized = normalizeRouterUrl(url);
7692
+ const pathOnly = normalized.split('?')[0].split('#')[0];
7693
+ if (!IN_TAB_ERROR_PATHS.has(pathOnly)) {
7694
+ return null;
7695
+ }
7696
+ const params = new URLSearchParams(normalized.split('?')[1] ?? '');
7697
+ const from = params.get(AXP_IN_TAB_ERROR_FROM_PARAM);
7698
+ if (!from?.startsWith('/')) {
7699
+ return null;
7700
+ }
7701
+ const fromPath = from.split('?')[0].split('#')[0];
7702
+ if (IN_TAB_ERROR_PATHS.has(fromPath)) {
7703
+ return null;
7704
+ }
7705
+ return normalizeRouterUrl(from);
7706
+ }
7707
+ /** URL a tab should open again. An in-tab error page retries its `from` route. */
7708
+ function axpInTabErrorRetryUrl(url) {
7709
+ return axpInTabErrorSourceUrl(url) ?? normalizeRouterUrl(url);
7710
+ }
7711
+ /** In-shell error URL whose tab identity is `sourceUrl`, not a single app-wide error route. */
7712
+ function axpInTabErrorLocation(kind, sourceUrl) {
7713
+ const path = inTabErrorPath(kind);
7714
+ const source = axpInTabErrorSourceUrl(sourceUrl) ?? sourceUrl;
7715
+ const params = new URLSearchParams();
7716
+ params.set(AXP_IN_TAB_ERROR_FROM_PARAM, source);
7717
+ return `${path}?${params.toString()}`;
7718
+ }
7719
+ /** UrlTree for guards and resolvers. The shell stays mounted; the tab key stays the source route. */
7720
+ function axpInTabErrorUrlTree(router, kind, sourceUrl) {
7721
+ return router.parseUrl(axpInTabErrorLocation(kind, sourceUrl));
7722
+ }
7723
+ //#endregion
7724
+ //#region ---- Unknown Route Guard ----
7725
+ /**
7726
+ * Sends an unmatched URL to the in-shell 404 without dropping the root layout.
7727
+ * `from` keeps that URL as its own tab.
7728
+ */
7729
+ const axpRedirectUnknownToNotFound = (_route, state) => {
7730
+ const router = inject(Router);
7731
+ return axpInTabErrorUrlTree(router, '404', state.url);
7732
+ };
7733
+ //#endregion
7734
+ //#region ---- Helpers ----
7735
+ function inTabErrorPath(kind) {
7736
+ switch (kind) {
7737
+ case '401':
7738
+ return '/error/401';
7739
+ case '404':
7740
+ return '/error/404';
7741
+ case 'offline':
7742
+ return '/error/offline';
7743
+ }
7744
+ }
7745
+ //#endregion
7746
+
7302
7747
  //#region ---- Types ----
7303
7748
  const AXP_HOME_TAB_KEY = 'axp:home';
7304
7749
  /** Route `data` key for tab-navigation behavior (`data.tab`). */
@@ -7306,9 +7751,20 @@ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
7306
7751
  //#endregion
7307
7752
 
7308
7753
  //#region ---- Route Data ----
7309
- /** True when the route declares itself as a scoped tab root (`data.tab = true`). */
7754
+ /**
7755
+ * True when this route node itself sets `data.tab = true`.
7756
+ * Pass the route's own config data. Activated snapshots merge parent `data`, so `snapshot.data`
7757
+ * is not a declaration — a child would look like its own tab root.
7758
+ */
7310
7759
  function axpHasTabScope(data) {
7311
- return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
7760
+ if (!data) {
7761
+ return false;
7762
+ }
7763
+ return data[AXP_TAB_ROUTE_DATA_KEY] === true;
7764
+ }
7765
+ /** True when this snapshot's route config declares `data.tab`, ignoring data inherited from parents. */
7766
+ function axpSnapshotDeclaresTabScope(snapshot) {
7767
+ return axpHasTabScope(snapshot.routeConfig?.data);
7312
7768
  }
7313
7769
  //#endregion
7314
7770
  //#region ---- Snapshot Resolution ----
@@ -7328,19 +7784,27 @@ function axpGetLeafRouteSnapshot(root) {
7328
7784
  return route;
7329
7785
  }
7330
7786
  /**
7331
- * Resolves the tab dedupe key from an activated route tree.
7332
- * Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
7787
+ * Path identity from an activated route tree, without query params.
7788
+ * Uses the nearest ancestor whose own route config sets `data.tab = true`; otherwise the leaf path.
7789
+ * Child navigations under that boundary (settings groups, profile sections) stay on the same tab.
7333
7790
  */
7334
- function axpResolveTabKeyFromSnapshot(leaf) {
7791
+ function axpTabScopePathFromSnapshot(leaf) {
7335
7792
  const chain = leaf.pathFromRoot;
7336
7793
  for (let index = chain.length - 1; index >= 0; index--) {
7337
7794
  const snapshot = chain[index];
7338
- if (axpHasTabScope(snapshot.data)) {
7795
+ if (axpSnapshotDeclaresTabScope(snapshot)) {
7339
7796
  return axpPathFromSnapshot(snapshot);
7340
7797
  }
7341
7798
  }
7342
7799
  return axpPathFromSnapshot(leaf);
7343
7800
  }
7801
+ /**
7802
+ * Resolves the tab dedupe key from an activated route tree.
7803
+ * Entity list routes also include navigation query params (`mode`, `list`, …), same as simple navigation.
7804
+ */
7805
+ function axpResolveTabKeyFromSnapshot(leaf) {
7806
+ return axpEntityListNavigationTabKey(axpTabScopePathFromSnapshot(leaf), leaf.queryParams);
7807
+ }
7344
7808
  //#endregion
7345
7809
  //#region ---- Router / URL Resolution ----
7346
7810
  /**
@@ -7349,17 +7813,29 @@ function axpResolveTabKeyFromSnapshot(leaf) {
7349
7813
  */
7350
7814
  function axpResolveTabKey(router, url) {
7351
7815
  const normalized = normalizeRouterUrl(url ?? router.url);
7816
+ const errorSource = axpInTabErrorSourceUrl(normalized);
7817
+ if (errorSource) {
7818
+ return axpResolveTabKey(router, errorSource);
7819
+ }
7352
7820
  const pathOnly = normalized.split('?')[0].split('#')[0];
7821
+ // Entity lists key off the URL itself. Route-config matching and the live snapshot can
7822
+ // disagree until navigation finishes, and that split was opening a second tab for one click.
7823
+ // `mode` and `list` stay in the key; `view`, `layout`, `filters`, and `_ts` do not.
7824
+ if (axpIsEntityListPath(pathOnly)) {
7825
+ return axpEntityListNavigationTabKey(pathOnly, normalized);
7826
+ }
7353
7827
  const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
7828
+ let basePath;
7354
7829
  if (!url || pathOnly === currentPath) {
7355
7830
  const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
7356
- return axpResolveTabKeyFromSnapshot(leaf);
7831
+ basePath = axpTabScopePathFromSnapshot(leaf);
7357
7832
  }
7358
- const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
7359
- if (fromConfig) {
7360
- return fromConfig;
7833
+ else {
7834
+ basePath =
7835
+ axpResolveTabKeyFromRouteConfig(router.config, pathOnly) ??
7836
+ (pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`);
7361
7837
  }
7362
- return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
7838
+ return axpEntityListNavigationTabKey(basePath, normalized);
7363
7839
  }
7364
7840
  /**
7365
7841
  * Matches a URL path against registered routes and resolves the tab key from `data.tab`.
@@ -7367,19 +7843,19 @@ function axpResolveTabKey(router, url) {
7367
7843
  function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
7368
7844
  const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
7369
7845
  const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
7370
- const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
7846
+ const chain = matchRouteConfigChain(routes, segments, 0, []);
7371
7847
  if (!chain?.length) {
7372
7848
  return null;
7373
7849
  }
7374
7850
  for (let index = chain.length - 1; index >= 0; index--) {
7375
7851
  const level = chain[index];
7376
- if (axpHasTabScope(level.mergedData)) {
7852
+ if (axpHasTabScope(level.ownData)) {
7377
7853
  return level.path;
7378
7854
  }
7379
7855
  }
7380
7856
  return chain[chain.length - 1]?.path ?? null;
7381
7857
  }
7382
- function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
7858
+ function matchRouteConfigChain(routes, segments, startIndex, chain) {
7383
7859
  let bestMatch = null;
7384
7860
  for (const route of routes) {
7385
7861
  if (route.redirectTo) {
@@ -7391,12 +7867,12 @@ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chai
7391
7867
  }
7392
7868
  const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
7393
7869
  const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
7394
- const mergedData = { ...inheritedData, ...(route.data ?? {}) };
7395
- const level = { mergedData, path };
7870
+ const ownData = { ...(route.data ?? {}) };
7871
+ const level = { ownData, path };
7396
7872
  const nextChain = [...chain, level];
7397
7873
  const nextIndex = startIndex + segmentMatch.consumed;
7398
7874
  if (route.children?.length) {
7399
- const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
7875
+ const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, nextChain);
7400
7876
  if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
7401
7877
  bestMatch = childMatch;
7402
7878
  }
@@ -7552,6 +8028,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7552
8028
  //#region ----- Command sub-flow navigation (session) -----
7553
8029
  const SUPPRESS_TAB_RESTORE_KEY = 'axp:command-subflow-suppress-tab-restore';
7554
8030
  const RETURN_URL_KEY = 'axp:command-subflow-return-url';
8031
+ const subFlowEndedListeners = new Set();
8032
+ /** Fires after a command sub-flow releases tab restore. Returns an unsubscribe function. */
8033
+ function axpOnCommandSubFlowNavigationEnded(listener) {
8034
+ subFlowEndedListeners.add(listener);
8035
+ return () => {
8036
+ subFlowEndedListeners.delete(listener);
8037
+ };
8038
+ }
7555
8039
  /** Marks an in-flight command sub-flow (file picker, crop, dialog) so tab restore must not hijack the route. */
7556
8040
  function axpBeginCommandSubFlowNavigation(returnUrl) {
7557
8041
  const trimmed = returnUrl.trim();
@@ -7591,11 +8075,14 @@ function axpEndCommandSubFlowNavigation() {
7591
8075
  catch {
7592
8076
  // sessionStorage may be unavailable
7593
8077
  }
8078
+ for (const listener of subFlowEndedListeners) {
8079
+ listener();
8080
+ }
7594
8081
  }
7595
8082
  //#endregion
7596
8083
 
7597
8084
  //#region ---- Tab Route Policy ----
7598
- const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
8085
+ const EXCLUDED_PATH_PREFIXES = ['/auth'];
7599
8086
  const EXCLUDED_PATH_PATTERNS = [
7600
8087
  /^\/assessment\/fill\//i,
7601
8088
  /^\/forms\//i,
@@ -7647,7 +8134,28 @@ class AXPTabNavigationService {
7647
8134
  ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
7648
8135
  this.activeTabKey = signal(null, /* @ts-ignore */
7649
8136
  ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
7650
- this.suppressNavigationSync = false;
8137
+ /** Nested navigations (menu + route `replaceUrl`) must not clear each other's sync guard. */
8138
+ this.navigationSyncSuppressDepth = 0;
8139
+ this.navigationSettingsChain = Promise.resolve();
8140
+ /** `tenantId:applicationId:userId` while the tab strip belongs to that session. Null after sign-out. */
8141
+ this.tabsOwnerStamp = null;
8142
+ /** False until the first session emission. A later stamp change must not restore saved tabs. */
8143
+ this.sessionTabsBound = false;
8144
+ /** Set when the user actually signs out, so the next sign-in does not reopen that list. */
8145
+ this.closedTabsForSessionChange = false;
8146
+ /** Storage key of the session that owned the strip. Survives sign-out, when the user id is already gone. */
8147
+ this.ownedStorageKey = null;
8148
+ /**
8149
+ * Stamp captured when readiness dropped but the user was still signed in.
8150
+ * A failed tenant/application switch returns to this stamp and must keep the saved list.
8151
+ */
8152
+ this.pendingReadinessGap = null;
8153
+ /** False until the saved list has been read, so an earlier route sync cannot overwrite it. */
8154
+ this.sessionTabsHydrated = false;
8155
+ /** Restore was skipped because a command sub-flow was open. Resumed when that sub-flow ends. */
8156
+ this.restoreDeferred = false;
8157
+ /** Incremented on every session emission. A queued task stops when a newer emission exists. */
8158
+ this.sessionSyncGeneration = 0;
7651
8159
  //#endregion
7652
8160
  //#region ---- Computed ----
7653
8161
  /** Effective mode: mobile always uses simple navigation. */
@@ -7667,6 +8175,10 @@ class AXPTabNavigationService {
7667
8175
  this.bindSettingsChanges();
7668
8176
  this.bindSessionChanges();
7669
8177
  this.bindRouteCacheSync();
8178
+ const stopSubFlowWatch = axpOnCommandSubFlowNavigationEnded(() => {
8179
+ void this.enqueueNavigationSettingsSync(() => this.resumeDeferredTabRestore());
8180
+ });
8181
+ this.destroyRef.onDestroy(stopSubFlowWatch);
7670
8182
  }
7671
8183
  //#endregion
7672
8184
  //#region ---- Public API ----
@@ -7706,10 +8218,9 @@ class AXPTabNavigationService {
7706
8218
  return;
7707
8219
  }
7708
8220
  // Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
7709
- this.suppressNavigationSync = true;
7710
- try {
8221
+ await this.runWithSuppressedNavigationSync(async () => {
7711
8222
  await axpNavigateAppPath(this.router, normalizedUrl);
7712
- const key = axpResolveTabKey(this.router);
8223
+ const key = axpResolveTabKey(this.router, normalizedUrl);
7713
8224
  const postNavExisting = this.findTabForUrl(normalizedUrl);
7714
8225
  if (postNavExisting) {
7715
8226
  if (postNavExisting.key !== key) {
@@ -7724,10 +8235,7 @@ class AXPTabNavigationService {
7724
8235
  this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
7725
8236
  this.activeTabKey.set(key);
7726
8237
  this.persistSession();
7727
- }
7728
- finally {
7729
- this.suppressNavigationSync = false;
7730
- }
8238
+ });
7731
8239
  }
7732
8240
  /**
7733
8241
  * Activates a tab and navigates to its URL. The previously active page is detached and cached by
@@ -7742,18 +8250,15 @@ class AXPTabNavigationService {
7742
8250
  if (!tab) {
7743
8251
  return;
7744
8252
  }
7745
- const targetUrl = urlOverride ?? tab.url;
8253
+ const targetUrl = axpInTabErrorRetryUrl(urlOverride ?? tab.url);
8254
+ if (tab.url !== targetUrl) {
8255
+ this.updateTabUrl(key, targetUrl);
8256
+ }
7746
8257
  this.touchTab(key);
7747
8258
  this.activeTabKey.set(key);
7748
8259
  this.persistSession();
7749
8260
  const navigate = async () => {
7750
- this.suppressNavigationSync = true;
7751
- try {
7752
- await this.router.navigateByUrl(targetUrl);
7753
- }
7754
- finally {
7755
- this.suppressNavigationSync = false;
7756
- }
8261
+ await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
7757
8262
  };
7758
8263
  if (suspendDeactivateGuard) {
7759
8264
  await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
@@ -7765,6 +8270,24 @@ class AXPTabNavigationService {
7765
8270
  async closeTab(key) {
7766
8271
  await this.closeTabs([key]);
7767
8272
  }
8273
+ /**
8274
+ * Drops the active error tab without opening a neighbor.
8275
+ * Home stays. The caller navigates to the real home page afterwards.
8276
+ */
8277
+ closeActiveErrorTab() {
8278
+ if (this.effectiveMode() !== 'tabular') {
8279
+ return;
8280
+ }
8281
+ const activeKey = this.activeTabKey();
8282
+ const tab = this.tabs().find((item) => item.key === activeKey);
8283
+ if (!tab || tab.key === AXP_HOME_TAB_KEY) {
8284
+ return;
8285
+ }
8286
+ this.tabs.update((items) => items.filter((item) => item.key !== tab.key));
8287
+ this.tabPreviewService.clearPreviews(new Set([tab.key]));
8288
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
8289
+ this.persistSession();
8290
+ }
7768
8291
  /** Closes every closable tab except the given key (pinned tabs are kept). */
7769
8292
  async closeOtherTabs(keepKey) {
7770
8293
  const keys = this.tabs()
@@ -7946,15 +8469,9 @@ class AXPTabNavigationService {
7946
8469
  targetKey = home.key;
7947
8470
  targetUrl = home.url;
7948
8471
  }
7949
- this.suppressNavigationSync = true;
7950
- try {
7951
- const navigated = await this.router.navigateByUrl(targetUrl);
7952
- if (!navigated) {
7953
- return false;
7954
- }
7955
- }
7956
- finally {
7957
- this.suppressNavigationSync = false;
8472
+ const navigated = await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
8473
+ if (!navigated) {
8474
+ return false;
7958
8475
  }
7959
8476
  this.activeTabKey.set(targetKey);
7960
8477
  this.touchTab(targetKey);
@@ -7984,6 +8501,16 @@ class AXPTabNavigationService {
7984
8501
  }
7985
8502
  //#endregion
7986
8503
  //#region ---- Router Sync ----
8504
+ /** Holds route-sync until every nested navigation in this turn has finished. */
8505
+ async runWithSuppressedNavigationSync(action) {
8506
+ this.navigationSyncSuppressDepth += 1;
8507
+ try {
8508
+ return await action();
8509
+ }
8510
+ finally {
8511
+ this.navigationSyncSuppressDepth = Math.max(0, this.navigationSyncSuppressDepth - 1);
8512
+ }
8513
+ }
7987
8514
  bindRouterEvents() {
7988
8515
  this.router.events
7989
8516
  .pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
@@ -7992,7 +8519,7 @@ class AXPTabNavigationService {
7992
8519
  });
7993
8520
  }
7994
8521
  async handleNavigationEnd(url) {
7995
- if (this.suppressNavigationSync) {
8522
+ if (this.navigationSyncSuppressDepth > 0) {
7996
8523
  return;
7997
8524
  }
7998
8525
  if (this.effectiveMode() === 'simple') {
@@ -8003,67 +8530,292 @@ class AXPTabNavigationService {
8003
8530
  return;
8004
8531
  }
8005
8532
  await this.ensureHomeTab();
8006
- if (this.isHomePageUrl(normalizedUrl)) {
8007
- this.activateHomeTabForUrl(normalizedUrl);
8533
+ const errorSource = axpInTabErrorSourceUrl(normalizedUrl);
8534
+ const storedUrl = errorSource ?? normalizedUrl;
8535
+ if (this.isHomePageUrl(storedUrl)) {
8536
+ this.activateHomeTabForUrl(storedUrl);
8008
8537
  return;
8009
8538
  }
8010
- const key = axpResolveTabKey(this.router, normalizedUrl);
8011
- const existing = this.findTabForUrl(normalizedUrl);
8539
+ const key = axpResolveTabKey(this.router, storedUrl);
8540
+ const existing = this.findTabForUrl(storedUrl);
8012
8541
  if (existing) {
8013
8542
  if (existing.key !== key) {
8014
8543
  this.migrateTabKey(existing.key, key);
8015
8544
  }
8016
- this.updateTabUrl(key, normalizedUrl);
8545
+ this.updateTabUrl(key, storedUrl);
8017
8546
  this.touchTab(key);
8018
8547
  this.activeTabKey.set(key);
8019
8548
  this.persistSession();
8020
8549
  return;
8021
8550
  }
8022
- this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
8551
+ this.insertTab(this.createTabDescriptor(storedUrl, errorSource ? this.resolveTabTitle(errorSource) : undefined, key));
8023
8552
  this.activeTabKey.set(key);
8024
8553
  this.persistSession();
8025
8554
  }
8026
8555
  //#endregion
8027
8556
  //#region ---- Settings ----
8028
8557
  async loadSettings() {
8029
- const [mode, maxTabs] = await Promise.all([
8030
- this.settingsService.get(AXPCommonSettings.NavigationMode),
8031
- this.settingsService.get(AXPCommonSettings.MaxOpenTabs),
8032
- ]);
8033
- this.navigationMode.set(mode === 'tabular' ? 'tabular' : 'simple');
8034
- this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
8035
- await this.restoreSessionIfNeeded();
8558
+ await this.enqueueNavigationSettingsSync(async () => {
8559
+ const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
8560
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
8561
+ await this.applyEffectiveNavigationMode();
8562
+ });
8036
8563
  }
8037
8564
  bindSettingsChanges() {
8565
+ this.settingsService.onLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
8566
+ void this.enqueueNavigationSettingsSync(() => this.applyEffectiveNavigationMode());
8567
+ });
8038
8568
  this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
8039
- if (event.keys.includes(AXPCommonSettings.NavigationMode)) {
8040
- const raw = event.values[AXPCommonSettings.NavigationMode];
8041
- const newMode = raw === 'tabular' ? 'tabular' : 'simple';
8042
- if (newMode !== this.navigationMode()) {
8043
- this.navigationMode.set(newMode);
8044
- if (this.effectiveMode() === 'simple') {
8045
- this.clearSession();
8046
- }
8047
- else {
8048
- void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
8049
- }
8050
- }
8051
- }
8052
- if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
8053
- this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
8054
- this.enforceMaxTabs();
8055
- this.persistSession();
8569
+ const modeChanged = event.keys.includes(AXPCommonSettings.NavigationMode);
8570
+ const maxChanged = event.keys.includes(AXPCommonSettings.MaxOpenTabs);
8571
+ if (!modeChanged && !maxChanged) {
8572
+ return;
8056
8573
  }
8574
+ void this.enqueueNavigationSettingsSync(async () => {
8575
+ if (modeChanged) {
8576
+ await this.applyEffectiveNavigationMode();
8577
+ }
8578
+ if (maxChanged) {
8579
+ const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
8580
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
8581
+ this.enforceMaxTabs();
8582
+ this.persistSession();
8583
+ }
8584
+ });
8057
8585
  });
8058
8586
  }
8059
8587
  bindSessionChanges() {
8060
- this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
8061
- if (axpIsCommandSubFlowNavigationSuspended()) {
8062
- return;
8588
+ combineLatest([
8589
+ this.sessionService.user$.pipe(map$1((user) => user?.id ?? null)),
8590
+ this.sessionService.tenant$.pipe(map$1((tenant) => tenant?.id ?? null)),
8591
+ this.sessionService.application$.pipe(map$1((application) => application?.id ?? null)),
8592
+ this.sessionService.isReadyForAppScopedApi$,
8593
+ ])
8594
+ .pipe(distinctUntilChanged$1((previous, next) => previous[0] === next[0] &&
8595
+ previous[1] === next[1] &&
8596
+ previous[2] === next[2] &&
8597
+ previous[3] === next[3]), takeUntilDestroyed(this.destroyRef))
8598
+ .subscribe(() => {
8599
+ const context = this.captureSessionSyncContext();
8600
+ if (this.tabsOwnerStamp != null && (context.sessionEnded || context.stamp !== this.tabsOwnerStamp)) {
8601
+ this.routeCacheRegistry.suspendAttach();
8063
8602
  }
8064
- void this.restoreSessionIfNeeded();
8603
+ void this.enqueueNavigationSettingsSync(() => this.syncTabsToSession(context));
8065
8604
  });
8066
8605
  }
8606
+ /** Reads the session once, so a later user cannot replace this event's storage key. */
8607
+ captureSessionSyncContext() {
8608
+ return {
8609
+ generation: ++this.sessionSyncGeneration,
8610
+ stamp: this.sessionStamp(),
8611
+ sessionEnded: !this.sessionService.user?.id || !this.sessionService.hasSessionCredentials(),
8612
+ storageKey: this.sessionStorageKey(),
8613
+ url: normalizeRouterUrl(this.router.url),
8614
+ };
8615
+ }
8616
+ isCurrentSessionSync(context) {
8617
+ return context.generation === this.sessionSyncGeneration;
8618
+ }
8619
+ /**
8620
+ * Closes every tab when the signed-in user, tenant, or application actually changes, including sign-out.
8621
+ * The saved list is deleted only for that real change, so a failed switch can restore it.
8622
+ * The context is the session at enqueue time. A newer emission stops this task, including after an await.
8623
+ */
8624
+ async syncTabsToSession(context) {
8625
+ // Only a bound signed-in owner is a real sign-out. Startup publishes the user
8626
+ // before Authenticated; treating that as sign-out deletes the saved tab list.
8627
+ // A queued sign-out still deletes its captured key after a newer sign-in.
8628
+ if (context.sessionEnded) {
8629
+ if (this.tabsOwnerStamp != null) {
8630
+ this.applyCapturedSignOut(context);
8631
+ }
8632
+ return;
8633
+ }
8634
+ if (!this.isCurrentSessionSync(context)) {
8635
+ return;
8636
+ }
8637
+ if (!this.sessionTabsBound) {
8638
+ this.sessionTabsBound = true;
8639
+ this.tabsOwnerStamp = context.stamp;
8640
+ this.ownedStorageKey = context.storageKey;
8641
+ if (!context.stamp) {
8642
+ return;
8643
+ }
8644
+ this.routeCacheRegistry.bindOwner(context.stamp);
8645
+ await this.applyEffectiveNavigationMode();
8646
+ if (!this.isCurrentSessionSync(context)) {
8647
+ return;
8648
+ }
8649
+ await this.restoreSessionIfNeeded(context);
8650
+ return;
8651
+ }
8652
+ if (context.stamp === this.tabsOwnerStamp &&
8653
+ this.pendingReadinessGap == null &&
8654
+ !this.closedTabsForSessionChange) {
8655
+ this.routeCacheRegistry.resumeAttach();
8656
+ return;
8657
+ }
8658
+ if (context.stamp == null) {
8659
+ if (this.tabsOwnerStamp != null) {
8660
+ this.pendingReadinessGap = this.tabsOwnerStamp;
8661
+ }
8662
+ return;
8663
+ }
8664
+ const gap = this.pendingReadinessGap;
8665
+ this.pendingReadinessGap = null;
8666
+ if (gap != null && gap === context.stamp && !this.closedTabsForSessionChange) {
8667
+ this.tabsOwnerStamp = context.stamp;
8668
+ this.routeCacheRegistry.resumeAttach();
8669
+ return;
8670
+ }
8671
+ const previous = gap ?? this.tabsOwnerStamp;
8672
+ const identityChanged = previous != null && previous !== context.stamp;
8673
+ if (identityChanged || this.closedTabsForSessionChange) {
8674
+ this.closedTabsForSessionChange = false;
8675
+ this.restoreDeferred = false;
8676
+ this.forgetPersistedTabs(this.ownedStorageKey);
8677
+ this.tabsOwnerStamp = context.stamp;
8678
+ this.ownedStorageKey = context.storageKey;
8679
+ this.sessionTabsHydrated = false;
8680
+ this.discardRuntimeTabs();
8681
+ this.routeCacheRegistry.bindOwner(context.stamp);
8682
+ this.routeCacheRegistry.resumeAttach();
8683
+ await this.applyEffectiveNavigationMode();
8684
+ if (!this.isCurrentSessionSync(context)) {
8685
+ return;
8686
+ }
8687
+ this.sessionTabsHydrated = true;
8688
+ await this.syncSettledDestination(context);
8689
+ return;
8690
+ }
8691
+ if (this.tabsOwnerStamp == null && context.stamp != null) {
8692
+ this.tabsOwnerStamp = context.stamp;
8693
+ this.ownedStorageKey = context.storageKey;
8694
+ this.routeCacheRegistry.bindOwner(context.stamp);
8695
+ this.routeCacheRegistry.resumeAttach();
8696
+ await this.applyEffectiveNavigationMode();
8697
+ if (!this.isCurrentSessionSync(context)) {
8698
+ return;
8699
+ }
8700
+ await this.restoreSessionIfNeeded(context);
8701
+ }
8702
+ }
8703
+ /**
8704
+ * Sign-out cleanup uses the captured storage key and still runs when a newer sign-in
8705
+ * has already superseded this event. It does not read the live session.
8706
+ */
8707
+ applyCapturedSignOut(context) {
8708
+ this.pendingReadinessGap = null;
8709
+ this.restoreDeferred = false;
8710
+ this.forgetPersistedTabs(context.storageKey ?? this.ownedStorageKey);
8711
+ this.tabsOwnerStamp = null;
8712
+ this.discardRuntimeTabs();
8713
+ this.routeCacheRegistry.resumeAttach();
8714
+ this.closedTabsForSessionChange = true;
8715
+ this.sessionTabsHydrated = false;
8716
+ }
8717
+ /**
8718
+ * Opens Home for the settled session. The current route is added only after the router
8719
+ * has left the URL captured with the event, so the previous context is not reopened.
8720
+ * Home is created even when that URL is unchanged, including when it is already Home.
8721
+ */
8722
+ async syncSettledDestination(context) {
8723
+ if (!this.isCurrentSessionSync(context) || this.effectiveMode() !== 'tabular') {
8724
+ return;
8725
+ }
8726
+ await this.ensureHomeTab();
8727
+ if (!this.isCurrentSessionSync(context)) {
8728
+ return;
8729
+ }
8730
+ const destination = normalizeRouterUrl(this.router.url);
8731
+ const routeBelongsToSettledSession = !!destination && destination !== context.url && axpIsTabEligibleUrl(destination);
8732
+ if (routeBelongsToSettledSession || (!!destination && this.isHomePageUrl(destination))) {
8733
+ await this.syncCurrentRouteAsTab();
8734
+ return;
8735
+ }
8736
+ if (this.tabs().some((tab) => tab.key === AXP_HOME_TAB_KEY)) {
8737
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
8738
+ }
8739
+ this.persistSession();
8740
+ }
8741
+ /** Continues a restore that was skipped while a command sub-flow held the router. */
8742
+ async resumeDeferredTabRestore() {
8743
+ if (!this.restoreDeferred || axpIsCommandSubFlowNavigationSuspended()) {
8744
+ return;
8745
+ }
8746
+ const context = {
8747
+ generation: this.sessionSyncGeneration,
8748
+ stamp: this.tabsOwnerStamp,
8749
+ sessionEnded: false,
8750
+ storageKey: this.ownedStorageKey,
8751
+ url: normalizeRouterUrl(this.router.url),
8752
+ };
8753
+ this.restoreDeferred = false;
8754
+ if (!context.stamp) {
8755
+ return;
8756
+ }
8757
+ await this.restoreSessionIfNeeded(context);
8758
+ }
8759
+ /** Identity of the session that may own open tabs. Null when the app session is not ready. */
8760
+ sessionStamp() {
8761
+ if (!this.sessionService.isReadyForAppScopedApi) {
8762
+ return null;
8763
+ }
8764
+ const userId = this.sessionService.user?.id;
8765
+ const tenantId = this.sessionService.tenant?.id;
8766
+ const applicationId = this.sessionService.application?.id;
8767
+ if (!userId || !tenantId || !applicationId) {
8768
+ return null;
8769
+ }
8770
+ return `${tenantId}:${applicationId}:${userId}`;
8771
+ }
8772
+ /** Deletes one captured tab list. A newer user's key is left untouched. */
8773
+ forgetPersistedTabs(storageKey) {
8774
+ if (!storageKey) {
8775
+ return;
8776
+ }
8777
+ sessionStorage.removeItem(storageKey);
8778
+ if (this.ownedStorageKey === storageKey) {
8779
+ this.ownedStorageKey = null;
8780
+ }
8781
+ }
8782
+ /** Closes every tab and destroys cached page components. */
8783
+ discardRuntimeTabs() {
8784
+ this.tabs.set([]);
8785
+ this.activeTabKey.set(null);
8786
+ this.tabPreviewService.clearAll();
8787
+ this.routeCacheRegistry.bindOwner(this.tabsOwnerStamp);
8788
+ }
8789
+ /**
8790
+ * Reads the effective NavigationMode (User, then Tenant, then Platform, then the definition default).
8791
+ * Scope reloads update the settings cache without `onChanged`, so callers must use this instead of
8792
+ * treating a partial event value as "switch to simple".
8793
+ */
8794
+ async applyEffectiveNavigationMode() {
8795
+ if (!this.sessionService.isReadyForAppScopedApi) {
8796
+ return;
8797
+ }
8798
+ const mode = await this.settingsService.get(AXPCommonSettings.NavigationMode);
8799
+ const next = mode === 'tabular' ? 'tabular' : 'simple';
8800
+ const previous = this.navigationMode();
8801
+ if (next === previous) {
8802
+ return;
8803
+ }
8804
+ this.navigationMode.set(next);
8805
+ if (previous === 'tabular' && next === 'simple') {
8806
+ this.clearSession();
8807
+ return;
8808
+ }
8809
+ if (next === 'tabular' && this.effectiveMode() === 'tabular' && this.sessionTabsHydrated) {
8810
+ await this.ensureHomeTab();
8811
+ await this.syncCurrentRouteAsTab();
8812
+ }
8813
+ }
8814
+ enqueueNavigationSettingsSync(task) {
8815
+ const run = this.navigationSettingsChain.then(task, task);
8816
+ this.navigationSettingsChain = run.then(() => undefined, () => undefined);
8817
+ return run;
8818
+ }
8067
8819
  //#endregion
8068
8820
  //#region ---- Tab Management ----
8069
8821
  async ensureHomeTab() {
@@ -8105,7 +8857,7 @@ class AXPTabNavigationService {
8105
8857
  }
8106
8858
  this.tabs.update((items) => {
8107
8859
  const withoutDuplicate = items.filter((item) => {
8108
- if (item.key === tab.key) {
8860
+ if (item.key === tab.key || this.isSameEntityListTab(item, tab)) {
8109
8861
  return false;
8110
8862
  }
8111
8863
  return axpResolveTabKey(this.router, item.url) !== tab.key;
@@ -8165,14 +8917,29 @@ class AXPTabNavigationService {
8165
8917
  lastActivatedAt: now,
8166
8918
  };
8167
8919
  }
8168
- /** Finds a tab by scoped key or legacy full-path key for the same URL. */
8920
+ /** Finds a tab by scoped key, or by entity-list identity when an older key drifted. */
8169
8921
  findTabForUrl(url) {
8170
8922
  if (this.isHomePageUrl(url)) {
8171
8923
  return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
8172
8924
  }
8173
8925
  const resolvedKey = axpResolveTabKey(this.router, url);
8174
- return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
8926
+ const byKey = this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
8175
8927
  (tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
8928
+ if (byKey) {
8929
+ return byKey;
8930
+ }
8931
+ if (!axpIsEntityListPath(url)) {
8932
+ return undefined;
8933
+ }
8934
+ const identity = axpBuildTabKey(normalizeRouterUrl(url));
8935
+ return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY && axpIsEntityListPath(tab.url) && axpBuildTabKey(tab.url) === identity);
8936
+ }
8937
+ /** Same list menu (`mode`, `list`, …). Page-state query params do not make a new tab. */
8938
+ isSameEntityListTab(left, right) {
8939
+ if (!axpIsEntityListPath(left.url) || !axpIsEntityListPath(right.url)) {
8940
+ return false;
8941
+ }
8942
+ return axpBuildTabKey(left.url) === axpBuildTabKey(right.url);
8176
8943
  }
8177
8944
  /** True when the URL is the configured application home page (e.g. dashboard). */
8178
8945
  isHomePageUrl(url) {
@@ -8317,8 +9084,12 @@ class AXPTabNavigationService {
8317
9084
  return `${SESSION_STORAGE_PREFIX}(${userId})`;
8318
9085
  }
8319
9086
  persistSession() {
8320
- const key = this.sessionStorageKey();
8321
- if (!key || this.effectiveMode() === 'simple') {
9087
+ const key = this.ownedStorageKey;
9088
+ if (!key ||
9089
+ key !== this.sessionStorageKey() ||
9090
+ this.tabsOwnerStamp !== this.sessionStamp() ||
9091
+ !this.sessionTabsHydrated ||
9092
+ this.effectiveMode() === 'simple') {
8322
9093
  return;
8323
9094
  }
8324
9095
  const payload = {
@@ -8335,31 +9106,54 @@ class AXPTabNavigationService {
8335
9106
  this.tabs.set([]);
8336
9107
  this.activeTabKey.set(null);
8337
9108
  this.tabPreviewService.clearAll();
9109
+ this.routeCacheRegistry.clear();
8338
9110
  }
8339
- async restoreSessionIfNeeded() {
9111
+ async restoreSessionIfNeeded(context) {
9112
+ if (!this.isCurrentSessionSync(context)) {
9113
+ return;
9114
+ }
8340
9115
  if (this.effectiveMode() === 'simple') {
9116
+ this.sessionTabsHydrated = true;
8341
9117
  return;
8342
9118
  }
8343
9119
  if (axpIsCommandSubFlowNavigationSuspended()) {
9120
+ this.restoreDeferred = true;
8344
9121
  return;
8345
9122
  }
8346
- const storageKey = this.sessionStorageKey();
9123
+ const storageKey = context.storageKey;
8347
9124
  if (!storageKey) {
8348
- await this.ensureHomeTab();
8349
- await this.syncCurrentRouteAsTab();
9125
+ this.tabs.set([]);
9126
+ this.activeTabKey.set(null);
9127
+ this.tabPreviewService.clearAll();
9128
+ this.sessionTabsHydrated = true;
8350
9129
  return;
8351
9130
  }
8352
9131
  const raw = sessionStorage.getItem(storageKey);
8353
9132
  if (!raw) {
9133
+ this.tabs.set([]);
9134
+ this.activeTabKey.set(null);
9135
+ this.sessionTabsHydrated = true;
8354
9136
  await this.ensureHomeTab();
9137
+ if (!this.isCurrentSessionSync(context)) {
9138
+ return;
9139
+ }
8355
9140
  await this.syncCurrentRouteAsTab();
8356
9141
  return;
8357
9142
  }
8358
9143
  try {
8359
9144
  const parsed = JSON.parse(raw);
8360
- const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
9145
+ const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
9146
+ .filter((tab) => tab?.key && tab?.url)
9147
+ .map((tab) => ({ ...tab, url: axpInTabErrorRetryUrl(tab.url) }))));
9148
+ if (!this.isCurrentSessionSync(context)) {
9149
+ return;
9150
+ }
9151
+ this.sessionTabsHydrated = true;
8361
9152
  this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
8362
9153
  await this.ensureHomeTab();
9154
+ if (!this.isCurrentSessionSync(context)) {
9155
+ return;
9156
+ }
8363
9157
  const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
8364
9158
  this.activeTabKey.set(activeKey);
8365
9159
  const activeTab = this.tabs().find((tab) => tab.key === activeKey);
@@ -8367,17 +9161,18 @@ class AXPTabNavigationService {
8367
9161
  if (activeTab &&
8368
9162
  currentUrl !== normalizeRouterUrl(activeTab.url) &&
8369
9163
  axpIsTabEligibleUrl(currentUrl)) {
8370
- this.suppressNavigationSync = true;
8371
- try {
8372
- await this.router.navigateByUrl(activeTab.url);
8373
- }
8374
- finally {
8375
- this.suppressNavigationSync = false;
8376
- }
9164
+ await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(activeTab.url));
8377
9165
  }
8378
9166
  }
8379
9167
  catch {
9168
+ if (!this.isCurrentSessionSync(context)) {
9169
+ return;
9170
+ }
9171
+ this.sessionTabsHydrated = true;
8380
9172
  await this.ensureHomeTab();
9173
+ if (!this.isCurrentSessionSync(context)) {
9174
+ return;
9175
+ }
8381
9176
  await this.syncCurrentRouteAsTab();
8382
9177
  }
8383
9178
  }
@@ -8627,7 +9422,12 @@ class AXPLocalEntityOutboxService {
8627
9422
  this.handlers.delete(entityKey);
8628
9423
  }
8629
9424
  pendingFor(entityKey) {
8630
- return this.pendingOps().filter((op) => op.entityKey === entityKey);
9425
+ const userKey = this.resolveUserKey();
9426
+ return this.pendingOps().filter((op) => op.entityKey === entityKey && op.userKey === userKey);
9427
+ }
9428
+ /** Await durable operations before a feature overlays pending rows after reload. */
9429
+ hydrate() {
9430
+ return this.refreshPending();
8631
9431
  }
8632
9432
  async enqueue(input) {
8633
9433
  const userKey = this.resolveUserKey();
@@ -8638,10 +9438,7 @@ class AXPLocalEntityOutboxService {
8638
9438
  }
8639
9439
  const recordIds = [...new Set((input.recordIds ?? []).map((id) => id?.trim()).filter(Boolean))];
8640
9440
  const existing = await this.loadUserOps(userKey);
8641
- const duplicate = existing.find((op) => op.entityKey === entityKey &&
8642
- op.action === action &&
8643
- sameIdSet(op.recordIds, recordIds) &&
8644
- stablePayload(op.payload) === stablePayload(input.payload));
9441
+ const duplicate = existing.find((op) => op.entityKey === entityKey && op.action === action && sameIdSet(op.recordIds, recordIds) && stablePayload(op.payload) === stablePayload(input.payload));
8645
9442
  if (duplicate) {
8646
9443
  return;
8647
9444
  }
@@ -8652,7 +9449,8 @@ class AXPLocalEntityOutboxService {
8652
9449
  action,
8653
9450
  recordIds,
8654
9451
  payload: input.payload,
8655
- queuedAt: new Date().toISOString(),
9452
+ // Keep dependent operations ordered even if send and edit enqueue in the same millisecond.
9453
+ queuedAt: new Date(Math.max(Date.now(), ...existing.map((op) => Date.parse(op.queuedAt) + 1))).toISOString(),
8656
9454
  attempts: 0,
8657
9455
  lastError: null,
8658
9456
  };
@@ -8690,6 +9488,10 @@ class AXPLocalEntityOutboxService {
8690
9488
  }
8691
9489
  const ops = (await this.loadUserOps(userKey)).sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
8692
9490
  for (const op of ops) {
9491
+ // A queued batch must never continue under another account after an awaited HTTP call.
9492
+ if (userKey !== this.resolveUserKey() || !this.sessionService.isReadyForAppScopedApi) {
9493
+ break;
9494
+ }
8693
9495
  const handler = this.handlers.get(op.entityKey);
8694
9496
  if (!handler) {
8695
9497
  continue;
@@ -8745,6 +9547,8 @@ class AXPLocalEntityOutboxService {
8745
9547
  return;
8746
9548
  }
8747
9549
  const ops = await this.loadUserOps(userKey);
9550
+ if (userKey !== this.resolveUserKey())
9551
+ return;
8748
9552
  ops.sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
8749
9553
  this.pendingOps.set(ops);
8750
9554
  }
@@ -9268,6 +10072,23 @@ class AXPNotFoundError extends Error {
9268
10072
  }
9269
10073
  }
9270
10074
 
10075
+ /**
10076
+ * Placeholder for a wildcard route whose guard always redirects into the shell.
10077
+ * The component is not shown.
10078
+ */
10079
+ class AXPUnreachableRouteComponent {
10080
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10081
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXPUnreachableRouteComponent, isStandalone: true, selector: "axp-unreachable-route", ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10082
+ }
10083
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, decorators: [{
10084
+ type: Component,
10085
+ args: [{
10086
+ selector: 'axp-unreachable-route',
10087
+ template: '',
10088
+ changeDetection: ChangeDetectionStrategy.OnPush,
10089
+ }]
10090
+ }] });
10091
+
9271
10092
  /** Canonical route path for the platform 404 page. */
9272
10093
  const AXP_NOT_FOUND_ROUTE = '/error/404';
9273
10094
  /**
@@ -9276,7 +10097,8 @@ const AXP_NOT_FOUND_ROUTE = '/error/404';
9276
10097
  */
9277
10098
  const AXP_NOT_FOUND_CATCH_ALL_ROUTE = {
9278
10099
  path: '**',
9279
- redirectTo: AXP_NOT_FOUND_ROUTE,
10100
+ canActivate: [axpRedirectUnknownToNotFound],
10101
+ component: AXPUnreachableRouteComponent,
9280
10102
  };
9281
10103
  /**
9282
10104
  * Registers the platform catch-all 404 route.
@@ -9300,8 +10122,8 @@ function axpIsEntityRecordNotFound(data) {
9300
10122
  /**
9301
10123
  * Creates an Angular redirect command to the platform 404 page.
9302
10124
  */
9303
- function axpRedirectToNotFound(router = inject(Router)) {
9304
- return new RedirectCommand(router.parseUrl(AXP_NOT_FOUND_ROUTE));
10125
+ function axpRedirectToNotFound(router = inject(Router), sourceUrl) {
10126
+ return new RedirectCommand(axpInTabErrorUrlTree(router, '404', sourceUrl ?? router.url));
9305
10127
  }
9306
10128
 
9307
10129
  /** Canonical route path for the platform 401 page. */
@@ -9309,8 +10131,8 @@ const AXP_UNAUTHORIZED_ROUTE = '/error/401';
9309
10131
  /**
9310
10132
  * Creates an Angular redirect command to the platform 401 page.
9311
10133
  */
9312
- function axpRedirectToUnauthorized(router = inject(Router)) {
9313
- return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
10134
+ function axpRedirectToUnauthorized(router = inject(Router), sourceUrl) {
10135
+ return new RedirectCommand(axpInTabErrorUrlTree(router, '401', sourceUrl ?? router.url));
9314
10136
  }
9315
10137
 
9316
10138
  //#endregion
@@ -10078,15 +10900,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10078
10900
  const AXP_ENTITY_VIEW_ACCESS = new InjectionToken('AXP_ENTITY_VIEW_ACCESS');
10079
10901
 
10080
10902
  const APP_CHOOSER_ROUTE = '/auth/account/app-chooser';
10081
- /** In-app navigations (e.g. Open Details) stay put instead of redirecting to 401. */
10082
- function axpShouldStayOnCurrentPage(router, targetUrl) {
10083
- const currentPath = router.url.split('#')[0]?.split('?')[0] ?? '';
10084
- const targetPath = targetUrl.split('#')[0]?.split('?')[0] ?? '';
10085
- if (!currentPath || currentPath === '/' || currentPath === targetPath) {
10086
- return false;
10087
- }
10088
- return !currentPath.startsWith('/auth') && !currentPath.startsWith('/error');
10089
- }
10090
10903
  /**
10091
10904
 
10092
10905
  * Blocks navigation when the target route is not accessible for the current user.
@@ -10114,10 +10927,7 @@ const AXPMenuRouteGuard = (route, state) => {
10114
10927
  if (isAllowed instanceof UrlTree) {
10115
10928
  return isAllowed;
10116
10929
  }
10117
- if (axpShouldStayOnCurrentPage(router, state.url)) {
10118
- return false;
10119
- }
10120
- return router.parseUrl(AXP_UNAUTHORIZED_ROUTE);
10930
+ return axpInTabErrorUrlTree(router, '401', state.url);
10121
10931
  }));
10122
10932
  };
10123
10933
 
@@ -11175,7 +11985,7 @@ class AXPCommonModule {
11175
11985
  provide: AXP_SETTING_DEFINITION_PROVIDER,
11176
11986
  useFactory: async () => {
11177
11987
  const injector = inject(Injector);
11178
- const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs')).AXPCommonSettingProvider;
11988
+ const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
11179
11989
  return new provider(injector);
11180
11990
  },
11181
11991
  multi: true,
@@ -11298,7 +12108,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11298
12108
  provide: AXP_SETTING_DEFINITION_PROVIDER,
11299
12109
  useFactory: async () => {
11300
12110
  const injector = inject(Injector);
11301
- const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs')).AXPCommonSettingProvider;
12111
+ const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
11302
12112
  return new provider(injector);
11303
12113
  },
11304
12114
  multi: true,
@@ -11343,20 +12153,38 @@ const AXP_ROOT_CONFIG_TOKEN = new InjectionToken('AXP_ROOT_CONFIG_TOKEN');
11343
12153
 
11344
12154
  /**
11345
12155
  * Opens the native file picker and always settles — including when the user cancels.
11346
- * `AXFileService.choose()` can hang on cancel because no `change` event fires.
12156
+ * `AXFileService.choose()` only listens for `change`, so dismissing the dialog leaves that promise pending.
11347
12157
  */
11348
12158
  function chooseSystemFiles(options = {}) {
11349
- const document = inject(DOCUMENT);
12159
+ return openSystemFileDialog(inject(DOCUMENT), options);
12160
+ }
12161
+ /**
12162
+ * Same picker as {@link chooseSystemFiles}, with the document passed in so callers can `await` before opening it.
12163
+ */
12164
+ function openSystemFileDialog(doc, options = {}) {
12165
+ const view = doc.defaultView;
12166
+ if (!view) {
12167
+ return Promise.resolve([]);
12168
+ }
11350
12169
  const accept = options.accept ?? '';
11351
12170
  const multiple = options.multiple ?? false;
11352
12171
  return new Promise((resolve) => {
11353
- const input = document.createElement('input');
12172
+ const input = doc.createElement('input');
11354
12173
  input.type = 'file';
11355
12174
  input.accept = accept;
11356
12175
  input.multiple = multiple;
11357
12176
  input.style.display = 'none';
11358
- document.body.appendChild(input);
12177
+ doc.body.appendChild(input);
11359
12178
  let settled = false;
12179
+ let dialogOpened = false;
12180
+ let watchFocus = false;
12181
+ const cleanup = () => {
12182
+ input.removeEventListener('change', onChange);
12183
+ input.removeEventListener('cancel', onCancel);
12184
+ view.removeEventListener('blur', onBlur);
12185
+ view.removeEventListener('focus', onFocus);
12186
+ input.remove();
12187
+ };
11360
12188
  const settle = (files) => {
11361
12189
  if (settled) {
11362
12190
  return;
@@ -11365,21 +12193,37 @@ function chooseSystemFiles(options = {}) {
11365
12193
  cleanup();
11366
12194
  resolve(files);
11367
12195
  };
11368
- const cleanup = () => {
11369
- input.removeEventListener('change', onChange);
11370
- window.removeEventListener('focus', onWindowFocus);
11371
- input.remove();
11372
- };
11373
12196
  const onChange = () => {
11374
12197
  settle(Array.from(input.files ?? []));
11375
12198
  };
11376
- const onWindowFocus = () => {
11377
- // Allow `change` to fire first when the user confirms a selection.
11378
- window.setTimeout(() => settle([]), 300);
12199
+ const onCancel = () => {
12200
+ settle([]);
12201
+ };
12202
+ const onBlur = () => {
12203
+ if (watchFocus) {
12204
+ dialogOpened = true;
12205
+ }
12206
+ };
12207
+ const onFocus = () => {
12208
+ if (!watchFocus || !dialogOpened) {
12209
+ return;
12210
+ }
12211
+ // `change` fires before this timeout when the user confirms a file.
12212
+ view.setTimeout(() => {
12213
+ if (!settled) {
12214
+ settle(Array.from(input.files ?? []));
12215
+ }
12216
+ }, 300);
11379
12217
  };
11380
12218
  input.addEventListener('change', onChange);
11381
- window.addEventListener('focus', onWindowFocus, { once: true });
12219
+ input.addEventListener('cancel', onCancel);
12220
+ view.addEventListener('blur', onBlur);
12221
+ view.addEventListener('focus', onFocus);
11382
12222
  input.click();
12223
+ // Ignore blur/focus caused by opening the dialog itself. Cancel still settles via the `cancel` event.
12224
+ view.setTimeout(() => {
12225
+ watchFocus = true;
12226
+ }, 0);
11383
12227
  });
11384
12228
  }
11385
12229
  //#endregion
@@ -12213,8 +13057,11 @@ class AXPLocalEntityEngine {
12213
13057
  return bucket.syncInFlight;
12214
13058
  }
12215
13059
  bucket.syncInFlight = (async () => {
13060
+ const userKey = this.resolveUserKey();
12216
13061
  const result = await fetchHot();
12217
- await this.replaceAll(entityKey, result.items ?? []);
13062
+ if (userKey === this.resolveUserKey()) {
13063
+ await this.replaceAll(entityKey, result.items ?? []);
13064
+ }
12218
13065
  })().finally(() => {
12219
13066
  bucket.syncInFlight = null;
12220
13067
  });
@@ -12289,6 +13136,8 @@ class AXPLocalEntityEngine {
12289
13136
  await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, async () => {
12290
13137
  await axpLocalEntityIndexedDb.items.bulkDelete(storageIds);
12291
13138
  });
13139
+ if (userKey !== this.resolveUserKey())
13140
+ return;
12292
13141
  bucket.items.set(next);
12293
13142
  bucket.hydrated.set(true);
12294
13143
  this.publish({ type: 'store-changed', entityKey, userKey, originId: this.originId });
@@ -12347,10 +13196,9 @@ class AXPLocalEntityEngine {
12347
13196
  bucket.hydrated.set(true);
12348
13197
  return;
12349
13198
  }
12350
- const stored = await axpLocalEntityIndexedDb.items
12351
- .where('[entityKey+userKey]')
12352
- .equals([entityKey, userKey])
12353
- .toArray();
13199
+ const stored = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).toArray();
13200
+ if (userKey !== this.resolveUserKey())
13201
+ return;
12354
13202
  const items = this.prune(entityKey, stored.map((row) => row.payload));
12355
13203
  this.userKeySignal.set(userKey);
12356
13204
  bucket.items.set(items);
@@ -12373,10 +13221,7 @@ class AXPLocalEntityEngine {
12373
13221
  const keepIds = new Set(stored.map((row) => row.storageId));
12374
13222
  const metaId = buildLocalEntityMetaId(entityKey, userKey);
12375
13223
  await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, axpLocalEntityIndexedDb.meta, async () => {
12376
- const existing = await axpLocalEntityIndexedDb.items
12377
- .where('[entityKey+userKey]')
12378
- .equals([entityKey, userKey])
12379
- .primaryKeys();
13224
+ const existing = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).primaryKeys();
12380
13225
  const staleIds = existing.filter((id) => !keepIds.has(String(id)));
12381
13226
  if (staleIds.length > 0) {
12382
13227
  await axpLocalEntityIndexedDb.items.bulkDelete(staleIds);
@@ -12391,6 +13236,8 @@ class AXPLocalEntityEngine {
12391
13236
  lastSyncedAt: new Date().toISOString(),
12392
13237
  });
12393
13238
  });
13239
+ if (userKey !== this.resolveUserKey())
13240
+ return;
12394
13241
  this.userKeySignal.set(userKey);
12395
13242
  bucket.items.set(items);
12396
13243
  bucket.hydrated.set(true);
@@ -12412,6 +13259,8 @@ class AXPLocalEntityEngine {
12412
13259
  }
12413
13260
  this.userKeySignal.set(userKey);
12414
13261
  for (const [entityKey, bucket] of this.buckets) {
13262
+ bucket.items.set([]);
13263
+ bucket.hydrated.set(false);
12415
13264
  bucket.bootstrapPromise = null;
12416
13265
  bucket.hydratePromise = null;
12417
13266
  bucket.syncInFlight = null;
@@ -14054,5 +14903,5 @@ const axpPageGetContextQueryDefinition = defineQuery({
14054
14903
  * Generated bundle index. Do not edit.
14055
14904
  */
14056
14905
 
14057
- export { ALL_DEFAULT_OPERATORS, AXPAcorexFileTypeBridgeProvider, AXPAcorexFileTypeModule, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPBrowserTitleService, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDefaultMultiLanguageConfigService, AXPEntityDetailsNavTrailService, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileChooserSettings, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPIndexedDbSettingValueProvider, AXPIntegrationDomainProviderContext, AXPIntegrationDomainRegistryFacade, AXPIntegrationDomainRegistryService, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLocalEntityEngine, AXPLocalEntityIndexedDb, AXPLocalEntityOfflineRuntime, AXPLocalEntityOutboxService, AXPLocalEntityRealtimeBridge, AXPLockService, AXPLogoImageSrcResolverService, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateCommand, AXPNetworkConnectivityService, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPageGetContextQuery, AXPPageRunActionCommand, AXPPlatformBrandingService, AXPPlatformDefaultConfigs, AXPPlatformSettingsLoadState, AXPPlatformSettingsOfflineBootstrapService, AXPQueryEvaluatorScopeProvider, AXPQueryParamsEvaluatorScopeProvider, AXPRegionalCalendarService, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsBulkSyncService, AXPSettingsEvaluatorScopeProvider, AXPSettingsIndexedDb, AXPSettingsService, AXPStateMessageComponent, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPUserNotificationService, AXPVersioningService, 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_BREADCRUMB_COLLAPSE_HEAD_COUNT, AXP_BREADCRUMB_COLLAPSE_TAIL_COUNT, AXP_DOCUMENT_FILE_TYPE_PURPOSES, AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT, AXP_ENTITY_VIEW_ACCESS, AXP_EXPERIENCE_SETTINGS_GROUP, AXP_EXPERIENCE_SETTINGS_SECTIONS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_INTEGRATION_CONNECTION_ESTABLISHED, AXP_INTEGRATION_CONNECTION_LIFECYCLE, AXP_INTEGRATION_DOMAIN_PROVIDER, AXP_INTEGRATION_OAUTH_SERVICE, AXP_INTEGRATION_PROVIDER, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_SECRET_STORE, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_LOCALE_MANAGEMENT_PORT, AXP_LOCAL_ENTITY_BROADCAST_CHANNEL, AXP_LOCAL_ENTITY_DEFAULT_MAX_ITEMS, AXP_LOCAL_ENTITY_OUTBOX_LOCK, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_BRANDING_SETTING_KEYS, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_RUNTIME_QUERY_CACHE_DEFAULTS, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTINGS_CACHE_SYNC, AXP_SETTINGS_SYNC_DEBOUNCE_MS, AXP_SETTINGS_SYNC_KEYS, AXP_SETTINGS_SYNC_RETRY_BACKOFF_MS, AXP_SETTINGS_SYNC_TTL_MS, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_SCOPE_DESCENDANTS, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_BREADCRUMB_JOIN_SEPARATOR, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXP_USER_NOTIFICATION_SERVICE, AXVChangeType, AX_FILE_TYPE_METADATA_SUPPORT_BY_FILE_VIEWER, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LIST_DEFAULT_INSTANCE_ID, ENTITY_LIST_INSTANCE_QUERY_PARAM, ENVIRONMENT, EQ_OPER, FILE_VIEWER_PREVIEW_EXTENSIONS, 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, SETTINGS_TRACE_NS, SETTINGS_TRACE_TITLE, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationDurationMs, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationStatusTextParams, axpBackgroundOperationToEntityModel, axpBeginCommandSubFlowNavigation, axpBuildBackgroundOperation, axpBuildTabKey, axpClearEntityDetailsNavTrail, axpCloneBreadcrumbItems, axpCoerceBackgroundOperationDate, axpComposeEntityDetailsNavTrail, axpCreateBackgroundOperationId, axpEndCommandSubFlowNavigation, axpEnsureEntityListInstanceQueryParam, axpEntityKeyFromDetailsPath, axpFallbackTabTitle, axpFindMenuItemByRoute, axpFormatBackgroundOperationDurationMs, axpGetBreadcrumbNavigatePath, axpGetEntityDetailsNavTrail, axpGetEntityListInstanceIdFromParamMap, axpGetEntityListInstanceIdFromPath, axpGetEntityListInstanceIdFromQueryString, axpGetEntityListReturnUrl, axpGetLeafRouteSnapshot, axpHasTabScope, axpInterpolateNavigatePath, axpIsActiveBackgroundOperationStatus, axpIsBackgroundOperationI18nText, axpIsCommandSubFlowNavigationSuspended, axpIsCompletedBackgroundOperationStatus, axpIsCompletedPanelBackgroundOperation, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsInProgressPanelBackgroundOperation, axpIsLikelyNetworkFailure, axpIsSameEntityDetailsRecord, axpIsTabEligibleUrl, axpIsTrayVisibleBackgroundOperation, axpJoinTabTitleSegments, axpLocalEntityCanServeListLocally, axpLocalEntityHotListRequest, axpLocalEntityIndexedDb, axpLocalEntityRecordId, axpLocalEntitySortAt, axpLocalEntitySupportsOfflineWrite, axpLocalEntityToPayload, axpMenuItemListInstanceId, axpMenuPathsMatch, axpNavigateAppPath, axpNavigateCommandDefinition, axpNormalizeEntityListInstanceId, axpPageGetContextQueryDefinition, axpPageRunActionCommandDefinition, axpParseAppPath, axpParseEntityDetailsRoute, axpParseEntityListRoute, axpPathFromSnapshot, axpPeekCommandSubFlowReturnUrl, axpPlatformSettingsAvailableGuard, axpPrepareOriginNavTrail, axpRedirectToNotFound, axpRedirectToUnauthorized, axpReplaceEntityListRouteQueryParams, axpResolveNavigatePath, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, axpResolveTabTitleParts, axpSaveEntityDetailsNavTrail, axpSaveEntityListReturnUrl, axpSetEntityDetailsPageQuery, axpSettingsIndexedDb, axpSplitCollapsedBreadcrumbs, axpStampEntityListMenuInstancePaths, axpStripHomeBreadcrumb, axpStripTrailingCommandlessCrumbs, axpTrimNavTrailBeforeTarget, backgroundOperationEntityFactory, buildFileStorageDownloadUrl, buildLocalEntityMetaId, buildLocalEntityStorageId, buildScopedSettingsSnapshotId, chooseSystemFiles, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createDefaultSettingValueProviders, createEmptyScopedSettingsSnapshot, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createFileTypeOpenContext, createIndexedDbSettingValueProviders, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, extractFileExtension, extractHttpRequestErrorInfo, extractRequestErrorInfo, findManualPresetIdByOperation, formatEntityRowDisplayTitle, formatRequestError, hasFileTypeOpenHandler, hydrateSettingsValuesFromIndexedDb, inferSettingCoercionKind, isFileNameShownInViewer, isFileTypeShownInViewer, isFileViewerPreviewExtension, isOneWaySettingsIndexedDbCache, isPendingCascadeFilter, isSettingKeyCacheable, isSettingVisibleOnScope, isStoredImageFileId, normalizeAvailableLanguageCodes, normalizeDocumentFileTypePurposes, normalizeDocumentToFileListItem, normalizeOverridableAt, normalizePositiveNumberOption, normalizeRouterUrl, normalizeStoredImageRef, peelSettingValueKey, platformErrorI18nKey, primaryLanguageSubtag, printGraphicFromElement, projectSettingsValuesForIndexedDb, provideAXPUserNotificationService, provideDynamicHomePage, provideLocalEntityOfflineRuntime, provideMenuMiddleware, purgeLegacyTenantScopedSettingsSnapshots, resolveDateFilterPresets, resolveEffectiveAvailableLanguageCodes, resolveEntityCreateListBehavior, resolveEntityDetailsRouteAccess, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveExplorerFileDisplayName, resolveFileTypeDisplay, resolveFileViewerType, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, resolveStoredImageDisplayUrl, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, shouldPersistSettingsInIndexedDb, stripAccidentalNestedSettingParents, throwIfCommandFailed, toAXPRequestError, toExecuteCommandFailureResult, traceSettingsAppStartupLoadComplete, traceSettingsAppStartupLoadStarted, traceSettingsClientTool, traceSettingsFlush, traceSettingsLocalPersist, traceSettingsPageLoadComplete, traceSettingsPageLoadStarted, traceSettingsPageRouteLoad, traceSettingsRetryScheduled, traceSettingsRevalidateFailed, traceSettingsServerApiCall, traceSettingsServiceLoadComplete, traceSettingsServiceLoadFailed, traceSettingsServiceLoadStarted, traceSettingsServiceReload, traceSettingsSyncFailed, triggerBrowserDownload, tryOpenFileByCatalogType, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
14906
+ export { ALL_DEFAULT_OPERATORS, AXPAcorexFileTypeBridgeProvider, AXPAcorexFileTypeModule, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPBrowserTitleService, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDefaultMultiLanguageConfigService, AXPEntityDetailsNavTrailService, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileChooserSettings, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterDefaultOperatorService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPIndexedDbSettingValueProvider, AXPIntegrationDomainProviderContext, AXPIntegrationDomainRegistryFacade, AXPIntegrationDomainRegistryService, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLocalEntityEngine, AXPLocalEntityIndexedDb, AXPLocalEntityOfflineRuntime, AXPLocalEntityOutboxService, AXPLocalEntityRealtimeBridge, AXPLockService, AXPLogoImageSrcResolverService, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateCommand, AXPNetworkConnectivityService, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPageGetContextQuery, AXPPageRunActionCommand, AXPPlatformBrandingService, AXPPlatformDefaultConfigs, AXPPlatformSettingsLoadState, AXPPlatformSettingsOfflineBootstrapService, AXPQueryEvaluatorScopeProvider, AXPQueryParamsEvaluatorScopeProvider, AXPRegionalCalendarService, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsBulkSyncService, AXPSettingsEvaluatorScopeProvider, AXPSettingsIndexedDb, AXPSettingsService, AXPStateMessageComponent, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnreachableRouteComponent, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPUserNotificationService, AXPUserStatusFeatureService, AXPVersioningService, 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_BREADCRUMB_COLLAPSE_HEAD_COUNT, AXP_BREADCRUMB_COLLAPSE_TAIL_COUNT, AXP_DOCUMENT_FILE_TYPE_PURPOSES, AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT, AXP_ENTITY_VIEW_ACCESS, AXP_EXPERIENCE_SETTINGS_GROUP, AXP_EXPERIENCE_SETTINGS_SECTIONS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_FILTER_DEFAULT_OPERATORS_WIDGET_NAME, AXP_FILTER_DEFAULT_OPERATOR_WIDGETS, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_INTEGRATION_CONNECTION_ESTABLISHED, AXP_INTEGRATION_CONNECTION_LIFECYCLE, AXP_INTEGRATION_DOMAIN_PROVIDER, AXP_INTEGRATION_OAUTH_SERVICE, AXP_INTEGRATION_PROVIDER, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_SECRET_STORE, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_IN_TAB_ERROR_FROM_PARAM, AXP_LOCALE_MANAGEMENT_PORT, AXP_LOCAL_ENTITY_BROADCAST_CHANNEL, AXP_LOCAL_ENTITY_DEFAULT_MAX_ITEMS, AXP_LOCAL_ENTITY_OUTBOX_LOCK, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_BRANDING_SETTING_KEYS, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_RUNTIME_QUERY_CACHE_DEFAULTS, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTINGS_CACHE_SYNC, AXP_SETTINGS_SYNC_DEBOUNCE_MS, AXP_SETTINGS_SYNC_KEYS, AXP_SETTINGS_SYNC_RETRY_BACKOFF_MS, AXP_SETTINGS_SYNC_TTL_MS, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_SCOPE_DESCENDANTS, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_BREADCRUMB_JOIN_SEPARATOR, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXP_USER_NOTIFICATION_SERVICE, AXP_USER_STATUS_ENABLED_DEFAULT, AXVChangeType, AX_FILE_TYPE_METADATA_SUPPORT_BY_FILE_VIEWER, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LIST_DEFAULT_INSTANCE_ID, ENTITY_LIST_INSTANCE_QUERY_PARAM, ENVIRONMENT, EQ_OPER, FILE_VIEWER_PREVIEW_EXTENSIONS, 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, SETTINGS_TRACE_NS, SETTINGS_TRACE_TITLE, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationDurationMs, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationStatusTextParams, axpBackgroundOperationToEntityModel, axpBeginCommandSubFlowNavigation, axpBuildBackgroundOperation, axpBuildTabKey, axpClearEntityDetailsNavTrail, axpCloneBreadcrumbItems, axpCoerceBackgroundOperationDate, axpComposeEntityDetailsNavTrail, axpCreateBackgroundOperationId, axpEndCommandSubFlowNavigation, axpEnsureEntityListInstanceQueryParam, axpEntityKeyFromDetailsPath, axpEntityListNavigationTabKey, axpFallbackTabTitle, axpFindMenuItemByRoute, axpFormatBackgroundOperationDurationMs, axpGetBreadcrumbNavigatePath, axpGetEntityDetailsNavTrail, axpGetEntityListInstanceIdFromParamMap, axpGetEntityListInstanceIdFromPath, axpGetEntityListInstanceIdFromQueryString, axpGetEntityListReturnUrl, axpGetLeafRouteSnapshot, axpHasTabScope, axpInTabErrorLocation, axpInTabErrorRetryUrl, axpInTabErrorSourceUrl, axpInTabErrorUrlTree, axpInterpolateNavigatePath, axpIsActiveBackgroundOperationStatus, axpIsBackgroundOperationI18nText, axpIsCommandSubFlowNavigationSuspended, axpIsCompletedBackgroundOperationStatus, axpIsCompletedPanelBackgroundOperation, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsInProgressPanelBackgroundOperation, axpIsLikelyNetworkFailure, axpIsSameEntityDetailsRecord, axpIsTabEligibleUrl, axpIsTrayVisibleBackgroundOperation, axpJoinTabTitleSegments, axpLocalEntityCanServeListLocally, axpLocalEntityHotListRequest, axpLocalEntityIndexedDb, axpLocalEntityRecordId, axpLocalEntitySortAt, axpLocalEntitySupportsOfflineWrite, axpLocalEntityToPayload, axpMenuItemListInstanceId, axpMenuPathsMatch, axpNavigateAppPath, axpNavigateCommandDefinition, axpNormalizeEntityListInstanceId, axpOnCommandSubFlowNavigationEnded, axpPageGetContextQueryDefinition, axpPageRunActionCommandDefinition, axpParseAppPath, axpParseEntityDetailsRoute, axpParseEntityListRoute, axpPathFromSnapshot, axpPeekCommandSubFlowReturnUrl, axpPlatformSettingsAvailableGuard, axpPrepareOriginNavTrail, axpRedirectToNotFound, axpRedirectToUnauthorized, axpRedirectUnknownToNotFound, axpReplaceEntityListRouteQueryParams, axpResolveNavigatePath, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, axpResolveTabTitleParts, axpSaveEntityDetailsNavTrail, axpSaveEntityListReturnUrl, axpSetEntityDetailsPageQuery, axpSettingsIndexedDb, axpSnapshotDeclaresTabScope, axpSplitCollapsedBreadcrumbs, axpStampEntityListMenuInstancePaths, axpStripHomeBreadcrumb, axpStripTrailingCommandlessCrumbs, axpTabScopePathFromSnapshot, axpTrimNavTrailBeforeTarget, backgroundOperationEntityFactory, buildFileStorageDownloadUrl, buildLocalEntityMetaId, buildLocalEntityStorageId, buildScopedSettingsSnapshotId, chooseSystemFiles, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createDefaultSettingValueProviders, createEmptyScopedSettingsSnapshot, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createFileTypeOpenContext, createIndexedDbSettingValueProviders, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, extractFileExtension, extractHttpRequestErrorInfo, extractRequestErrorInfo, findFilterDefaultOperatorWidget, findManualPresetIdByOperation, formatEntityRowDisplayTitle, formatRequestError, hasFileTypeOpenHandler, hydrateSettingsValuesFromIndexedDb, inferSettingCoercionKind, isFileNameShownInViewer, isFileTypeShownInViewer, isFileViewerPreviewExtension, isOneWaySettingsIndexedDbCache, isPendingCascadeFilter, isSettingKeyCacheable, isSettingVisibleOnScope, isStoredImageFileId, normalizeAvailableLanguageCodes, normalizeDocumentFileTypePurposes, normalizeDocumentToFileListItem, normalizeOverridableAt, normalizePositiveNumberOption, normalizeRouterUrl, normalizeStoredImageRef, openSystemFileDialog, peelSettingValueKey, platformErrorI18nKey, primaryLanguageSubtag, printGraphicFromElement, projectSettingsValuesForIndexedDb, provideAXPUserNotificationService, provideDynamicHomePage, provideLocalEntityOfflineRuntime, provideMenuMiddleware, purgeLegacyTenantScopedSettingsSnapshots, readFilterDefaultOperatorMap, resolveDateFilterPresets, resolveEffectiveAvailableLanguageCodes, resolveEntityCreateListBehavior, resolveEntityDetailsRouteAccess, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveExplorerFileDisplayName, resolveFileTypeDisplay, resolveFileViewerType, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, resolveStoredFilterDefaultOperator, resolveStoredImageDisplayUrl, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, shouldPersistSettingsInIndexedDb, stripAccidentalNestedSettingParents, throwIfCommandFailed, toAXPRequestError, toExecuteCommandFailureResult, traceSettingsAppStartupLoadComplete, traceSettingsAppStartupLoadStarted, traceSettingsClientTool, traceSettingsFlush, traceSettingsLocalPersist, traceSettingsPageLoadComplete, traceSettingsPageLoadStarted, traceSettingsPageRouteLoad, traceSettingsRetryScheduled, traceSettingsRevalidateFailed, traceSettingsServerApiCall, traceSettingsServiceLoadComplete, traceSettingsServiceLoadFailed, traceSettingsServiceLoadStarted, traceSettingsServiceReload, traceSettingsSyncFailed, triggerBrowserDownload, tryOpenFileByCatalogType, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty, withFilterDefaultOperator, writeFilterDefaultOperatorOverride };
14058
14907
  //# sourceMappingURL=acorex-platform-framework-client-common.mjs.map