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

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 +1382 -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,272 @@ 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 queryString = query.includes('?')
3867
+ ? (query.split('#')[0].split('?')[1] ?? '')
3868
+ : query.replace(/^\?/, '');
3869
+ const params = new URLSearchParams(queryString);
3870
+ params.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
3871
+ return pairs;
3872
+ }
3873
+ if (typeof URLSearchParams !== 'undefined' && query instanceof URLSearchParams) {
3874
+ query.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
3875
+ return pairs;
3876
+ }
3877
+ for (const [key, value] of Object.entries(query)) {
3878
+ pushEntityListNavigationPair(pairs, key, value);
3879
+ }
3880
+ return pairs;
3437
3881
  }
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: () => [] });
3882
+ /**
3883
+ * Tab and route-cache identity for an entity list.
3884
+ * Navigation query params (`mode=cm`, `mode=pm`, non-default `list`, …) keep each menu distinct,
3885
+ * the same way simple navigation does. Page-state params stay on the URL and do not split the tab.
3886
+ */
3887
+ function axpEntityListNavigationTabKey(path, query) {
3888
+ const base = path.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
3889
+ if (!axpIsEntityListPath(base)) {
3890
+ return base;
3891
+ }
3892
+ const pairs = readEntityListNavigationPairs(query);
3893
+ if (!pairs.length) {
3894
+ return base;
3895
+ }
3896
+ pairs.sort((left, right) => left[0].localeCompare(right[0]) || left[1].localeCompare(right[1]));
3897
+ const queryString = pairs
3898
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
3899
+ .join('&');
3900
+ return `${base}?${queryString}`;
3901
+ }
3902
+ //#endregion
3444
3903
 
3445
3904
  //#endregion
3446
3905
  /**
@@ -3458,6 +3917,10 @@ class AXPTabRouteCacheRegistry {
3458
3917
  this.openKeys = new Set();
3459
3918
  this.enabled = false;
3460
3919
  this.deactivateGuardSuspended = false;
3920
+ /** While true, cached pages stay stored but must not be attached to a navigation. */
3921
+ this.attachSuspended = false;
3922
+ /** Session that owns cached pages. Null means signed out: never keep another user's components. */
3923
+ this.ownerKey = null;
3461
3924
  }
3462
3925
  //#endregion
3463
3926
  //#region ---- Deactivate Guard Suspension ----
@@ -3481,6 +3944,35 @@ class AXPTabRouteCacheRegistry {
3481
3944
  }
3482
3945
  //#endregion
3483
3946
  //#region ---- Mode ----
3947
+ /** True while a signed-in session may keep detached pages. */
3948
+ canCache() {
3949
+ return this.ownerKey !== null;
3950
+ }
3951
+ /** True while a session transition must not reattach a cached page. */
3952
+ isAttachSuspended() {
3953
+ return this.attachSuspended;
3954
+ }
3955
+ /**
3956
+ * Keeps cached pages for a failed switch, but blocks attaching them until the transition settles.
3957
+ */
3958
+ suspendAttach() {
3959
+ this.attachSuspended = true;
3960
+ }
3961
+ /** Allows attach again. Does not destroy the stored pages. */
3962
+ resumeAttach() {
3963
+ this.attachSuspended = false;
3964
+ }
3965
+ /**
3966
+ * Binds cached pages to one session. A sign-out or a different user destroys every cached
3967
+ * component so the next session cannot reattach the previous user's data.
3968
+ */
3969
+ bindOwner(ownerKey) {
3970
+ if (this.ownerKey === ownerKey) {
3971
+ return;
3972
+ }
3973
+ this.ownerKey = ownerKey;
3974
+ this.clear();
3975
+ }
3484
3976
  /** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
3485
3977
  setEnabled(enabled) {
3486
3978
  if (this.enabled === enabled) {
@@ -3502,7 +3994,11 @@ class AXPTabRouteCacheRegistry {
3502
3994
  */
3503
3995
  syncOpenKeys(scopedKeys, activeTabUrls = []) {
3504
3996
  const scopedSet = new Set(scopedKeys);
3505
- const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
3997
+ const activePathSet = new Set(activeTabUrls.flatMap((url) => {
3998
+ const pathOnly = url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
3999
+ const withNavigation = axpEntityListNavigationTabKey(pathOnly, url);
4000
+ return withNavigation === pathOnly ? [pathOnly] : [pathOnly, withNavigation];
4001
+ }));
3506
4002
  for (const cacheKey of [...this.handles.keys()]) {
3507
4003
  const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
3508
4004
  const keepsScopedKey = scopedSet.has(normalizedCacheKey);
@@ -3544,6 +4040,10 @@ class AXPTabRouteCacheRegistry {
3544
4040
  * destroying the old one would destroy the live component and corrupt the view (NG0922).
3545
4041
  */
3546
4042
  store(key, handle) {
4043
+ if (!this.ownerKey) {
4044
+ this.destroyHandle(handle);
4045
+ return;
4046
+ }
3547
4047
  this.handles.set(key, handle);
3548
4048
  }
3549
4049
  /**
@@ -3553,6 +4053,9 @@ class AXPTabRouteCacheRegistry {
3553
4053
  * `store(snapshot, null)`.
3554
4054
  */
3555
4055
  retrieve(key) {
4056
+ if (this.attachSuspended) {
4057
+ return null;
4058
+ }
3556
4059
  return this.handles.get(key) ?? null;
3557
4060
  }
3558
4061
  /**
@@ -3832,6 +4335,7 @@ const AXP_EXPERIENCE_SETTINGS_SECTIONS = {
3832
4335
  Performance: 'performance',
3833
4336
  Entity: 'entity',
3834
4337
  Offline: 'offline',
4338
+ Presence: 'presence',
3835
4339
  FileChooser: 'file-chooser',
3836
4340
  };
3837
4341
  //#endregion
@@ -5329,7 +5833,7 @@ class AXPBackgroundOperationSlotComponent {
5329
5833
  this.popover()?.close();
5330
5834
  }
5331
5835
  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 }); }
5836
+ 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
5837
  }
5334
5838
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, decorators: [{
5335
5839
  type: Component,
@@ -5600,148 +6104,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
5600
6104
  args: [{ providedIn: 'root' }]
5601
6105
  }] });
5602
6106
 
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
6107
  //#region ----- Menu instance identity -----
5746
6108
  /** Prefers the stamped `list` query param, then the menu item name. */
5747
6109
  function axpMenuItemListInstanceId(item) {
@@ -7051,6 +7413,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7051
7413
  function axpMenuItemNeedsUpdate(item, updates) {
7052
7414
  return Object.keys(updates).some((key) => item[key] !== updates[key]);
7053
7415
  }
7416
+ /** True when the menu command itself navigates. A path on the same item must not navigate again. */
7417
+ function axpMenuCommandIsNavigation(command) {
7418
+ if (!command || typeof command !== 'object') {
7419
+ return false;
7420
+ }
7421
+ if ('type' in command && command.type === 'router') {
7422
+ return true;
7423
+ }
7424
+ return 'name' in command && command.name === 'navigate';
7425
+ }
7054
7426
  /** Immutably patches the first menu item with the given name. */
7055
7427
  function axpUpdateMenuItemByName(items, name, updates) {
7056
7428
  let changed = false;
@@ -7124,18 +7496,22 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
7124
7496
  });
7125
7497
  },
7126
7498
  executeCommand(item) {
7127
- if (item.path) {
7128
- const path = item.name
7499
+ const path = item.path
7500
+ ? item.name
7129
7501
  ? axpEnsureEntityListInstanceQueryParam(item.path, item.name)
7130
- : item.path;
7502
+ : item.path
7503
+ : undefined;
7504
+ if (path) {
7131
7505
  void commandService.execute('navigate', { path });
7132
7506
  patchState(store, { selectedMenuItem: { item, isFullMatch: true } });
7133
7507
  }
7134
- if (item.command) {
7135
- const command = item.command;
7136
- if (commandService.exists(command?.name)) {
7137
- void commandService.execute(command.name, command.options ?? {});
7138
- }
7508
+ const command = item.command;
7509
+ if (!command || (path && axpMenuCommandIsNavigation(command))) {
7510
+ return;
7511
+ }
7512
+ const executeCommand = command;
7513
+ if (executeCommand.name && commandService.exists(executeCommand.name)) {
7514
+ void commandService.execute(executeCommand.name, executeCommand.options ?? {});
7139
7515
  }
7140
7516
  },
7141
7517
  isItemOpen(item) {
@@ -7248,13 +7624,14 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
7248
7624
  const IGNORED_QUERY_KEYS = new Set(['_ts']);
7249
7625
  /**
7250
7626
  * 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.
7627
+ * Path segments define the page. Entity-list navigation query params (`mode`, `list`, …)
7628
+ * stay in the key so two menus of the same entity stay distinct, matching simple navigation.
7629
+ * Page-state params (`view`, `filters`, `_ts`) stay on the URL and do not open another tab.
7253
7630
  */
7254
7631
  function axpBuildTabKey(url) {
7255
7632
  const normalized = normalizeRouterUrl(url);
7256
7633
  const pathOnly = normalized.split('?')[0].split('#')[0];
7257
- return pathOnly;
7634
+ return axpEntityListNavigationTabKey(pathOnly, normalized);
7258
7635
  }
7259
7636
  /** Normalizes a router URL for storage and navigation. */
7260
7637
  function normalizeRouterUrl(url) {
@@ -7299,6 +7676,73 @@ function axpFallbackTabTitle(url) {
7299
7676
  }
7300
7677
  //#endregion
7301
7678
 
7679
+ //#region ---- Constants ----
7680
+ /** Query param that keeps the failed route as the tab identity. */
7681
+ const AXP_IN_TAB_ERROR_FROM_PARAM = 'from';
7682
+ const IN_TAB_ERROR_PATHS = new Set(['/error/401', '/error/404', '/error/offline', '/401', '/404']);
7683
+ //#endregion
7684
+ //#region ---- URL Helpers ----
7685
+ /**
7686
+ * Original page URL carried on an in-shell error route.
7687
+ * Null when the URL is not an error page or `from` is missing.
7688
+ */
7689
+ function axpInTabErrorSourceUrl(url) {
7690
+ const normalized = normalizeRouterUrl(url);
7691
+ const pathOnly = normalized.split('?')[0].split('#')[0];
7692
+ if (!IN_TAB_ERROR_PATHS.has(pathOnly)) {
7693
+ return null;
7694
+ }
7695
+ const params = new URLSearchParams(normalized.split('?')[1] ?? '');
7696
+ const from = params.get(AXP_IN_TAB_ERROR_FROM_PARAM);
7697
+ if (!from?.startsWith('/')) {
7698
+ return null;
7699
+ }
7700
+ const fromPath = from.split('?')[0].split('#')[0];
7701
+ if (IN_TAB_ERROR_PATHS.has(fromPath)) {
7702
+ return null;
7703
+ }
7704
+ return normalizeRouterUrl(from);
7705
+ }
7706
+ /** URL a tab should open again. An in-tab error page retries its `from` route. */
7707
+ function axpInTabErrorRetryUrl(url) {
7708
+ return axpInTabErrorSourceUrl(url) ?? normalizeRouterUrl(url);
7709
+ }
7710
+ /** In-shell error URL whose tab identity is `sourceUrl`, not a single app-wide error route. */
7711
+ function axpInTabErrorLocation(kind, sourceUrl) {
7712
+ const path = inTabErrorPath(kind);
7713
+ const source = axpInTabErrorSourceUrl(sourceUrl) ?? sourceUrl;
7714
+ const params = new URLSearchParams();
7715
+ params.set(AXP_IN_TAB_ERROR_FROM_PARAM, source);
7716
+ return `${path}?${params.toString()}`;
7717
+ }
7718
+ /** UrlTree for guards and resolvers. The shell stays mounted; the tab key stays the source route. */
7719
+ function axpInTabErrorUrlTree(router, kind, sourceUrl) {
7720
+ return router.parseUrl(axpInTabErrorLocation(kind, sourceUrl));
7721
+ }
7722
+ //#endregion
7723
+ //#region ---- Unknown Route Guard ----
7724
+ /**
7725
+ * Sends an unmatched URL to the in-shell 404 without dropping the root layout.
7726
+ * `from` keeps that URL as its own tab.
7727
+ */
7728
+ const axpRedirectUnknownToNotFound = (_route, state) => {
7729
+ const router = inject(Router);
7730
+ return axpInTabErrorUrlTree(router, '404', state.url);
7731
+ };
7732
+ //#endregion
7733
+ //#region ---- Helpers ----
7734
+ function inTabErrorPath(kind) {
7735
+ switch (kind) {
7736
+ case '401':
7737
+ return '/error/401';
7738
+ case '404':
7739
+ return '/error/404';
7740
+ case 'offline':
7741
+ return '/error/offline';
7742
+ }
7743
+ }
7744
+ //#endregion
7745
+
7302
7746
  //#region ---- Types ----
7303
7747
  const AXP_HOME_TAB_KEY = 'axp:home';
7304
7748
  /** Route `data` key for tab-navigation behavior (`data.tab`). */
@@ -7306,9 +7750,20 @@ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
7306
7750
  //#endregion
7307
7751
 
7308
7752
  //#region ---- Route Data ----
7309
- /** True when the route declares itself as a scoped tab root (`data.tab = true`). */
7753
+ /**
7754
+ * True when this route node itself sets `data.tab = true`.
7755
+ * Pass the route's own config data. Activated snapshots merge parent `data`, so `snapshot.data`
7756
+ * is not a declaration — a child would look like its own tab root.
7757
+ */
7310
7758
  function axpHasTabScope(data) {
7311
- return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
7759
+ if (!data) {
7760
+ return false;
7761
+ }
7762
+ return data[AXP_TAB_ROUTE_DATA_KEY] === true;
7763
+ }
7764
+ /** True when this snapshot's route config declares `data.tab`, ignoring data inherited from parents. */
7765
+ function axpSnapshotDeclaresTabScope(snapshot) {
7766
+ return axpHasTabScope(snapshot.routeConfig?.data);
7312
7767
  }
7313
7768
  //#endregion
7314
7769
  //#region ---- Snapshot Resolution ----
@@ -7328,19 +7783,27 @@ function axpGetLeafRouteSnapshot(root) {
7328
7783
  return route;
7329
7784
  }
7330
7785
  /**
7331
- * Resolves the tab dedupe key from an activated route tree.
7332
- * Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
7786
+ * Path identity from an activated route tree, without query params.
7787
+ * Uses the nearest ancestor whose own route config sets `data.tab = true`; otherwise the leaf path.
7788
+ * Child navigations under that boundary (settings groups, profile sections) stay on the same tab.
7333
7789
  */
7334
- function axpResolveTabKeyFromSnapshot(leaf) {
7790
+ function axpTabScopePathFromSnapshot(leaf) {
7335
7791
  const chain = leaf.pathFromRoot;
7336
7792
  for (let index = chain.length - 1; index >= 0; index--) {
7337
7793
  const snapshot = chain[index];
7338
- if (axpHasTabScope(snapshot.data)) {
7794
+ if (axpSnapshotDeclaresTabScope(snapshot)) {
7339
7795
  return axpPathFromSnapshot(snapshot);
7340
7796
  }
7341
7797
  }
7342
7798
  return axpPathFromSnapshot(leaf);
7343
7799
  }
7800
+ /**
7801
+ * Resolves the tab dedupe key from an activated route tree.
7802
+ * Entity list routes also include navigation query params (`mode`, `list`, …), same as simple navigation.
7803
+ */
7804
+ function axpResolveTabKeyFromSnapshot(leaf) {
7805
+ return axpEntityListNavigationTabKey(axpTabScopePathFromSnapshot(leaf), leaf.queryParams);
7806
+ }
7344
7807
  //#endregion
7345
7808
  //#region ---- Router / URL Resolution ----
7346
7809
  /**
@@ -7349,17 +7812,29 @@ function axpResolveTabKeyFromSnapshot(leaf) {
7349
7812
  */
7350
7813
  function axpResolveTabKey(router, url) {
7351
7814
  const normalized = normalizeRouterUrl(url ?? router.url);
7815
+ const errorSource = axpInTabErrorSourceUrl(normalized);
7816
+ if (errorSource) {
7817
+ return axpResolveTabKey(router, errorSource);
7818
+ }
7352
7819
  const pathOnly = normalized.split('?')[0].split('#')[0];
7820
+ // Entity lists key off the URL itself. Route-config matching and the live snapshot can
7821
+ // disagree until navigation finishes, and that split was opening a second tab for one click.
7822
+ // `mode` and `list` stay in the key; `view`, `layout`, `filters`, and `_ts` do not.
7823
+ if (axpIsEntityListPath(pathOnly)) {
7824
+ return axpEntityListNavigationTabKey(pathOnly, normalized);
7825
+ }
7353
7826
  const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
7827
+ let basePath;
7354
7828
  if (!url || pathOnly === currentPath) {
7355
7829
  const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
7356
- return axpResolveTabKeyFromSnapshot(leaf);
7830
+ basePath = axpTabScopePathFromSnapshot(leaf);
7357
7831
  }
7358
- const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
7359
- if (fromConfig) {
7360
- return fromConfig;
7832
+ else {
7833
+ basePath =
7834
+ axpResolveTabKeyFromRouteConfig(router.config, pathOnly) ??
7835
+ (pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`);
7361
7836
  }
7362
- return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
7837
+ return axpEntityListNavigationTabKey(basePath, normalized);
7363
7838
  }
7364
7839
  /**
7365
7840
  * Matches a URL path against registered routes and resolves the tab key from `data.tab`.
@@ -7367,19 +7842,19 @@ function axpResolveTabKey(router, url) {
7367
7842
  function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
7368
7843
  const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
7369
7844
  const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
7370
- const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
7845
+ const chain = matchRouteConfigChain(routes, segments, 0, []);
7371
7846
  if (!chain?.length) {
7372
7847
  return null;
7373
7848
  }
7374
7849
  for (let index = chain.length - 1; index >= 0; index--) {
7375
7850
  const level = chain[index];
7376
- if (axpHasTabScope(level.mergedData)) {
7851
+ if (axpHasTabScope(level.ownData)) {
7377
7852
  return level.path;
7378
7853
  }
7379
7854
  }
7380
7855
  return chain[chain.length - 1]?.path ?? null;
7381
7856
  }
7382
- function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
7857
+ function matchRouteConfigChain(routes, segments, startIndex, chain) {
7383
7858
  let bestMatch = null;
7384
7859
  for (const route of routes) {
7385
7860
  if (route.redirectTo) {
@@ -7391,12 +7866,12 @@ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chai
7391
7866
  }
7392
7867
  const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
7393
7868
  const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
7394
- const mergedData = { ...inheritedData, ...(route.data ?? {}) };
7395
- const level = { mergedData, path };
7869
+ const ownData = { ...(route.data ?? {}) };
7870
+ const level = { ownData, path };
7396
7871
  const nextChain = [...chain, level];
7397
7872
  const nextIndex = startIndex + segmentMatch.consumed;
7398
7873
  if (route.children?.length) {
7399
- const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
7874
+ const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, nextChain);
7400
7875
  if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
7401
7876
  bestMatch = childMatch;
7402
7877
  }
@@ -7552,6 +8027,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7552
8027
  //#region ----- Command sub-flow navigation (session) -----
7553
8028
  const SUPPRESS_TAB_RESTORE_KEY = 'axp:command-subflow-suppress-tab-restore';
7554
8029
  const RETURN_URL_KEY = 'axp:command-subflow-return-url';
8030
+ const subFlowEndedListeners = new Set();
8031
+ /** Fires after a command sub-flow releases tab restore. Returns an unsubscribe function. */
8032
+ function axpOnCommandSubFlowNavigationEnded(listener) {
8033
+ subFlowEndedListeners.add(listener);
8034
+ return () => {
8035
+ subFlowEndedListeners.delete(listener);
8036
+ };
8037
+ }
7555
8038
  /** Marks an in-flight command sub-flow (file picker, crop, dialog) so tab restore must not hijack the route. */
7556
8039
  function axpBeginCommandSubFlowNavigation(returnUrl) {
7557
8040
  const trimmed = returnUrl.trim();
@@ -7591,11 +8074,14 @@ function axpEndCommandSubFlowNavigation() {
7591
8074
  catch {
7592
8075
  // sessionStorage may be unavailable
7593
8076
  }
8077
+ for (const listener of subFlowEndedListeners) {
8078
+ listener();
8079
+ }
7594
8080
  }
7595
8081
  //#endregion
7596
8082
 
7597
8083
  //#region ---- Tab Route Policy ----
7598
- const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
8084
+ const EXCLUDED_PATH_PREFIXES = ['/auth'];
7599
8085
  const EXCLUDED_PATH_PATTERNS = [
7600
8086
  /^\/assessment\/fill\//i,
7601
8087
  /^\/forms\//i,
@@ -7647,7 +8133,28 @@ class AXPTabNavigationService {
7647
8133
  ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
7648
8134
  this.activeTabKey = signal(null, /* @ts-ignore */
7649
8135
  ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
7650
- this.suppressNavigationSync = false;
8136
+ /** Nested navigations (menu + route `replaceUrl`) must not clear each other's sync guard. */
8137
+ this.navigationSyncSuppressDepth = 0;
8138
+ this.navigationSettingsChain = Promise.resolve();
8139
+ /** `tenantId:applicationId:userId` while the tab strip belongs to that session. Null after sign-out. */
8140
+ this.tabsOwnerStamp = null;
8141
+ /** False until the first session emission. A later stamp change must not restore saved tabs. */
8142
+ this.sessionTabsBound = false;
8143
+ /** Set when the user actually signs out, so the next sign-in does not reopen that list. */
8144
+ this.closedTabsForSessionChange = false;
8145
+ /** Storage key of the session that owned the strip. Survives sign-out, when the user id is already gone. */
8146
+ this.ownedStorageKey = null;
8147
+ /**
8148
+ * Stamp captured when readiness dropped but the user was still signed in.
8149
+ * A failed tenant/application switch returns to this stamp and must keep the saved list.
8150
+ */
8151
+ this.pendingReadinessGap = null;
8152
+ /** False until the saved list has been read, so an earlier route sync cannot overwrite it. */
8153
+ this.sessionTabsHydrated = false;
8154
+ /** Restore was skipped because a command sub-flow was open. Resumed when that sub-flow ends. */
8155
+ this.restoreDeferred = false;
8156
+ /** Incremented on every session emission. A queued task stops when a newer emission exists. */
8157
+ this.sessionSyncGeneration = 0;
7651
8158
  //#endregion
7652
8159
  //#region ---- Computed ----
7653
8160
  /** Effective mode: mobile always uses simple navigation. */
@@ -7667,6 +8174,10 @@ class AXPTabNavigationService {
7667
8174
  this.bindSettingsChanges();
7668
8175
  this.bindSessionChanges();
7669
8176
  this.bindRouteCacheSync();
8177
+ const stopSubFlowWatch = axpOnCommandSubFlowNavigationEnded(() => {
8178
+ void this.enqueueNavigationSettingsSync(() => this.resumeDeferredTabRestore());
8179
+ });
8180
+ this.destroyRef.onDestroy(stopSubFlowWatch);
7670
8181
  }
7671
8182
  //#endregion
7672
8183
  //#region ---- Public API ----
@@ -7706,10 +8217,9 @@ class AXPTabNavigationService {
7706
8217
  return;
7707
8218
  }
7708
8219
  // Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
7709
- this.suppressNavigationSync = true;
7710
- try {
8220
+ await this.runWithSuppressedNavigationSync(async () => {
7711
8221
  await axpNavigateAppPath(this.router, normalizedUrl);
7712
- const key = axpResolveTabKey(this.router);
8222
+ const key = axpResolveTabKey(this.router, normalizedUrl);
7713
8223
  const postNavExisting = this.findTabForUrl(normalizedUrl);
7714
8224
  if (postNavExisting) {
7715
8225
  if (postNavExisting.key !== key) {
@@ -7724,10 +8234,7 @@ class AXPTabNavigationService {
7724
8234
  this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
7725
8235
  this.activeTabKey.set(key);
7726
8236
  this.persistSession();
7727
- }
7728
- finally {
7729
- this.suppressNavigationSync = false;
7730
- }
8237
+ });
7731
8238
  }
7732
8239
  /**
7733
8240
  * Activates a tab and navigates to its URL. The previously active page is detached and cached by
@@ -7742,18 +8249,15 @@ class AXPTabNavigationService {
7742
8249
  if (!tab) {
7743
8250
  return;
7744
8251
  }
7745
- const targetUrl = urlOverride ?? tab.url;
8252
+ const targetUrl = axpInTabErrorRetryUrl(urlOverride ?? tab.url);
8253
+ if (tab.url !== targetUrl) {
8254
+ this.updateTabUrl(key, targetUrl);
8255
+ }
7746
8256
  this.touchTab(key);
7747
8257
  this.activeTabKey.set(key);
7748
8258
  this.persistSession();
7749
8259
  const navigate = async () => {
7750
- this.suppressNavigationSync = true;
7751
- try {
7752
- await this.router.navigateByUrl(targetUrl);
7753
- }
7754
- finally {
7755
- this.suppressNavigationSync = false;
7756
- }
8260
+ await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
7757
8261
  };
7758
8262
  if (suspendDeactivateGuard) {
7759
8263
  await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
@@ -7765,6 +8269,24 @@ class AXPTabNavigationService {
7765
8269
  async closeTab(key) {
7766
8270
  await this.closeTabs([key]);
7767
8271
  }
8272
+ /**
8273
+ * Drops the active error tab without opening a neighbor.
8274
+ * Home stays. The caller navigates to the real home page afterwards.
8275
+ */
8276
+ closeActiveErrorTab() {
8277
+ if (this.effectiveMode() !== 'tabular') {
8278
+ return;
8279
+ }
8280
+ const activeKey = this.activeTabKey();
8281
+ const tab = this.tabs().find((item) => item.key === activeKey);
8282
+ if (!tab || tab.key === AXP_HOME_TAB_KEY) {
8283
+ return;
8284
+ }
8285
+ this.tabs.update((items) => items.filter((item) => item.key !== tab.key));
8286
+ this.tabPreviewService.clearPreviews(new Set([tab.key]));
8287
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
8288
+ this.persistSession();
8289
+ }
7768
8290
  /** Closes every closable tab except the given key (pinned tabs are kept). */
7769
8291
  async closeOtherTabs(keepKey) {
7770
8292
  const keys = this.tabs()
@@ -7946,15 +8468,9 @@ class AXPTabNavigationService {
7946
8468
  targetKey = home.key;
7947
8469
  targetUrl = home.url;
7948
8470
  }
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;
8471
+ const navigated = await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
8472
+ if (!navigated) {
8473
+ return false;
7958
8474
  }
7959
8475
  this.activeTabKey.set(targetKey);
7960
8476
  this.touchTab(targetKey);
@@ -7984,6 +8500,16 @@ class AXPTabNavigationService {
7984
8500
  }
7985
8501
  //#endregion
7986
8502
  //#region ---- Router Sync ----
8503
+ /** Holds route-sync until every nested navigation in this turn has finished. */
8504
+ async runWithSuppressedNavigationSync(action) {
8505
+ this.navigationSyncSuppressDepth += 1;
8506
+ try {
8507
+ return await action();
8508
+ }
8509
+ finally {
8510
+ this.navigationSyncSuppressDepth = Math.max(0, this.navigationSyncSuppressDepth - 1);
8511
+ }
8512
+ }
7987
8513
  bindRouterEvents() {
7988
8514
  this.router.events
7989
8515
  .pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
@@ -7992,7 +8518,7 @@ class AXPTabNavigationService {
7992
8518
  });
7993
8519
  }
7994
8520
  async handleNavigationEnd(url) {
7995
- if (this.suppressNavigationSync) {
8521
+ if (this.navigationSyncSuppressDepth > 0) {
7996
8522
  return;
7997
8523
  }
7998
8524
  if (this.effectiveMode() === 'simple') {
@@ -8003,67 +8529,288 @@ class AXPTabNavigationService {
8003
8529
  return;
8004
8530
  }
8005
8531
  await this.ensureHomeTab();
8006
- if (this.isHomePageUrl(normalizedUrl)) {
8007
- this.activateHomeTabForUrl(normalizedUrl);
8532
+ const errorSource = axpInTabErrorSourceUrl(normalizedUrl);
8533
+ const storedUrl = errorSource ?? normalizedUrl;
8534
+ if (this.isHomePageUrl(storedUrl)) {
8535
+ this.activateHomeTabForUrl(storedUrl);
8008
8536
  return;
8009
8537
  }
8010
- const key = axpResolveTabKey(this.router, normalizedUrl);
8011
- const existing = this.findTabForUrl(normalizedUrl);
8538
+ const key = axpResolveTabKey(this.router, storedUrl);
8539
+ const existing = this.findTabForUrl(storedUrl);
8012
8540
  if (existing) {
8013
8541
  if (existing.key !== key) {
8014
8542
  this.migrateTabKey(existing.key, key);
8015
8543
  }
8016
- this.updateTabUrl(key, normalizedUrl);
8544
+ this.updateTabUrl(key, storedUrl);
8017
8545
  this.touchTab(key);
8018
8546
  this.activeTabKey.set(key);
8019
8547
  this.persistSession();
8020
8548
  return;
8021
8549
  }
8022
- this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
8550
+ this.insertTab(this.createTabDescriptor(storedUrl, errorSource ? this.resolveTabTitle(errorSource) : undefined, key));
8023
8551
  this.activeTabKey.set(key);
8024
8552
  this.persistSession();
8025
8553
  }
8026
8554
  //#endregion
8027
8555
  //#region ---- Settings ----
8028
8556
  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();
8557
+ await this.enqueueNavigationSettingsSync(async () => {
8558
+ const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
8559
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
8560
+ await this.applyEffectiveNavigationMode();
8561
+ });
8036
8562
  }
8037
8563
  bindSettingsChanges() {
8564
+ this.settingsService.onLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
8565
+ void this.enqueueNavigationSettingsSync(() => this.applyEffectiveNavigationMode());
8566
+ });
8038
8567
  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();
8568
+ const modeChanged = event.keys.includes(AXPCommonSettings.NavigationMode);
8569
+ const maxChanged = event.keys.includes(AXPCommonSettings.MaxOpenTabs);
8570
+ if (!modeChanged && !maxChanged) {
8571
+ return;
8056
8572
  }
8573
+ void this.enqueueNavigationSettingsSync(async () => {
8574
+ if (modeChanged) {
8575
+ await this.applyEffectiveNavigationMode();
8576
+ }
8577
+ if (maxChanged) {
8578
+ const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
8579
+ this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
8580
+ this.enforceMaxTabs();
8581
+ this.persistSession();
8582
+ }
8583
+ });
8057
8584
  });
8058
8585
  }
8059
8586
  bindSessionChanges() {
8060
- this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
8061
- if (axpIsCommandSubFlowNavigationSuspended()) {
8062
- return;
8587
+ combineLatest([
8588
+ this.sessionService.user$.pipe(map$1((user) => user?.id ?? null)),
8589
+ this.sessionService.tenant$.pipe(map$1((tenant) => tenant?.id ?? null)),
8590
+ this.sessionService.application$.pipe(map$1((application) => application?.id ?? null)),
8591
+ this.sessionService.isReadyForAppScopedApi$,
8592
+ ])
8593
+ .pipe(distinctUntilChanged$1((previous, next) => previous[0] === next[0] &&
8594
+ previous[1] === next[1] &&
8595
+ previous[2] === next[2] &&
8596
+ previous[3] === next[3]), takeUntilDestroyed(this.destroyRef))
8597
+ .subscribe(() => {
8598
+ const context = this.captureSessionSyncContext();
8599
+ if (this.tabsOwnerStamp != null && (context.sessionEnded || context.stamp !== this.tabsOwnerStamp)) {
8600
+ this.routeCacheRegistry.suspendAttach();
8063
8601
  }
8064
- void this.restoreSessionIfNeeded();
8602
+ void this.enqueueNavigationSettingsSync(() => this.syncTabsToSession(context));
8065
8603
  });
8066
8604
  }
8605
+ /** Reads the session once, so a later user cannot replace this event's storage key. */
8606
+ captureSessionSyncContext() {
8607
+ return {
8608
+ generation: ++this.sessionSyncGeneration,
8609
+ stamp: this.sessionStamp(),
8610
+ sessionEnded: !this.sessionService.user?.id || !this.sessionService.hasSessionCredentials(),
8611
+ storageKey: this.sessionStorageKey(),
8612
+ url: normalizeRouterUrl(this.router.url),
8613
+ };
8614
+ }
8615
+ isCurrentSessionSync(context) {
8616
+ return context.generation === this.sessionSyncGeneration;
8617
+ }
8618
+ /**
8619
+ * Closes every tab when the signed-in user, tenant, or application actually changes, including sign-out.
8620
+ * The saved list is deleted only for that real change, so a failed switch can restore it.
8621
+ * The context is the session at enqueue time. A newer emission stops this task, including after an await.
8622
+ */
8623
+ async syncTabsToSession(context) {
8624
+ // Sign-out still deletes its own captured list when a later sign-in made this event stale.
8625
+ if (context.sessionEnded) {
8626
+ this.applyCapturedSignOut(context);
8627
+ return;
8628
+ }
8629
+ if (!this.isCurrentSessionSync(context)) {
8630
+ return;
8631
+ }
8632
+ if (!this.sessionTabsBound) {
8633
+ this.sessionTabsBound = true;
8634
+ this.tabsOwnerStamp = context.stamp;
8635
+ this.ownedStorageKey = context.storageKey;
8636
+ if (!context.stamp) {
8637
+ return;
8638
+ }
8639
+ this.routeCacheRegistry.bindOwner(context.stamp);
8640
+ await this.applyEffectiveNavigationMode();
8641
+ if (!this.isCurrentSessionSync(context)) {
8642
+ return;
8643
+ }
8644
+ await this.restoreSessionIfNeeded(context);
8645
+ return;
8646
+ }
8647
+ if (context.stamp === this.tabsOwnerStamp &&
8648
+ this.pendingReadinessGap == null &&
8649
+ !this.closedTabsForSessionChange) {
8650
+ this.routeCacheRegistry.resumeAttach();
8651
+ return;
8652
+ }
8653
+ if (context.stamp == null) {
8654
+ if (this.tabsOwnerStamp != null) {
8655
+ this.pendingReadinessGap = this.tabsOwnerStamp;
8656
+ }
8657
+ return;
8658
+ }
8659
+ const gap = this.pendingReadinessGap;
8660
+ this.pendingReadinessGap = null;
8661
+ if (gap != null && gap === context.stamp && !this.closedTabsForSessionChange) {
8662
+ this.tabsOwnerStamp = context.stamp;
8663
+ this.routeCacheRegistry.resumeAttach();
8664
+ return;
8665
+ }
8666
+ const previous = gap ?? this.tabsOwnerStamp;
8667
+ const identityChanged = previous != null && previous !== context.stamp;
8668
+ if (identityChanged || this.closedTabsForSessionChange) {
8669
+ this.closedTabsForSessionChange = false;
8670
+ this.restoreDeferred = false;
8671
+ this.forgetPersistedTabs(this.ownedStorageKey);
8672
+ this.tabsOwnerStamp = context.stamp;
8673
+ this.ownedStorageKey = context.storageKey;
8674
+ this.sessionTabsHydrated = false;
8675
+ this.discardRuntimeTabs();
8676
+ this.routeCacheRegistry.bindOwner(context.stamp);
8677
+ this.routeCacheRegistry.resumeAttach();
8678
+ await this.applyEffectiveNavigationMode();
8679
+ if (!this.isCurrentSessionSync(context)) {
8680
+ return;
8681
+ }
8682
+ this.sessionTabsHydrated = true;
8683
+ await this.syncSettledDestination(context);
8684
+ return;
8685
+ }
8686
+ if (this.tabsOwnerStamp == null && context.stamp != null) {
8687
+ this.tabsOwnerStamp = context.stamp;
8688
+ this.ownedStorageKey = context.storageKey;
8689
+ this.routeCacheRegistry.bindOwner(context.stamp);
8690
+ this.routeCacheRegistry.resumeAttach();
8691
+ await this.applyEffectiveNavigationMode();
8692
+ if (!this.isCurrentSessionSync(context)) {
8693
+ return;
8694
+ }
8695
+ await this.restoreSessionIfNeeded(context);
8696
+ }
8697
+ }
8698
+ /**
8699
+ * Sign-out cleanup uses the captured storage key and still runs when a newer sign-in
8700
+ * has already superseded this event. It does not read the live session.
8701
+ */
8702
+ applyCapturedSignOut(context) {
8703
+ this.pendingReadinessGap = null;
8704
+ this.restoreDeferred = false;
8705
+ this.forgetPersistedTabs(context.storageKey ?? this.ownedStorageKey);
8706
+ this.tabsOwnerStamp = null;
8707
+ this.discardRuntimeTabs();
8708
+ this.routeCacheRegistry.resumeAttach();
8709
+ this.closedTabsForSessionChange = true;
8710
+ this.sessionTabsHydrated = false;
8711
+ }
8712
+ /**
8713
+ * Opens Home for the settled session. The current route is added only after the router
8714
+ * has left the URL captured with the event, so the previous context is not reopened.
8715
+ * Home is created even when that URL is unchanged, including when it is already Home.
8716
+ */
8717
+ async syncSettledDestination(context) {
8718
+ if (!this.isCurrentSessionSync(context) || this.effectiveMode() !== 'tabular') {
8719
+ return;
8720
+ }
8721
+ await this.ensureHomeTab();
8722
+ if (!this.isCurrentSessionSync(context)) {
8723
+ return;
8724
+ }
8725
+ const destination = normalizeRouterUrl(this.router.url);
8726
+ const routeBelongsToSettledSession = !!destination && destination !== context.url && axpIsTabEligibleUrl(destination);
8727
+ if (routeBelongsToSettledSession || (!!destination && this.isHomePageUrl(destination))) {
8728
+ await this.syncCurrentRouteAsTab();
8729
+ return;
8730
+ }
8731
+ if (this.tabs().some((tab) => tab.key === AXP_HOME_TAB_KEY)) {
8732
+ this.activeTabKey.set(AXP_HOME_TAB_KEY);
8733
+ }
8734
+ this.persistSession();
8735
+ }
8736
+ /** Continues a restore that was skipped while a command sub-flow held the router. */
8737
+ async resumeDeferredTabRestore() {
8738
+ if (!this.restoreDeferred || axpIsCommandSubFlowNavigationSuspended()) {
8739
+ return;
8740
+ }
8741
+ const context = {
8742
+ generation: this.sessionSyncGeneration,
8743
+ stamp: this.tabsOwnerStamp,
8744
+ sessionEnded: false,
8745
+ storageKey: this.ownedStorageKey,
8746
+ url: normalizeRouterUrl(this.router.url),
8747
+ };
8748
+ this.restoreDeferred = false;
8749
+ if (!context.stamp) {
8750
+ return;
8751
+ }
8752
+ await this.restoreSessionIfNeeded(context);
8753
+ }
8754
+ /** Identity of the session that may own open tabs. Null when the app session is not ready. */
8755
+ sessionStamp() {
8756
+ if (!this.sessionService.isReadyForAppScopedApi) {
8757
+ return null;
8758
+ }
8759
+ const userId = this.sessionService.user?.id;
8760
+ const tenantId = this.sessionService.tenant?.id;
8761
+ const applicationId = this.sessionService.application?.id;
8762
+ if (!userId || !tenantId || !applicationId) {
8763
+ return null;
8764
+ }
8765
+ return `${tenantId}:${applicationId}:${userId}`;
8766
+ }
8767
+ /** Deletes one captured tab list. A newer user's key is left untouched. */
8768
+ forgetPersistedTabs(storageKey) {
8769
+ if (!storageKey) {
8770
+ return;
8771
+ }
8772
+ sessionStorage.removeItem(storageKey);
8773
+ if (this.ownedStorageKey === storageKey) {
8774
+ this.ownedStorageKey = null;
8775
+ }
8776
+ }
8777
+ /** Closes every tab and destroys cached page components. */
8778
+ discardRuntimeTabs() {
8779
+ this.tabs.set([]);
8780
+ this.activeTabKey.set(null);
8781
+ this.tabPreviewService.clearAll();
8782
+ this.routeCacheRegistry.bindOwner(this.tabsOwnerStamp);
8783
+ }
8784
+ /**
8785
+ * Reads the effective NavigationMode (User, then Tenant, then Platform, then the definition default).
8786
+ * Scope reloads update the settings cache without `onChanged`, so callers must use this instead of
8787
+ * treating a partial event value as "switch to simple".
8788
+ */
8789
+ async applyEffectiveNavigationMode() {
8790
+ if (!this.sessionService.isReadyForAppScopedApi) {
8791
+ return;
8792
+ }
8793
+ const mode = await this.settingsService.get(AXPCommonSettings.NavigationMode);
8794
+ const next = mode === 'tabular' ? 'tabular' : 'simple';
8795
+ const previous = this.navigationMode();
8796
+ if (next === previous) {
8797
+ return;
8798
+ }
8799
+ this.navigationMode.set(next);
8800
+ if (previous === 'tabular' && next === 'simple') {
8801
+ this.clearSession();
8802
+ return;
8803
+ }
8804
+ if (next === 'tabular' && this.effectiveMode() === 'tabular' && this.sessionTabsHydrated) {
8805
+ await this.ensureHomeTab();
8806
+ await this.syncCurrentRouteAsTab();
8807
+ }
8808
+ }
8809
+ enqueueNavigationSettingsSync(task) {
8810
+ const run = this.navigationSettingsChain.then(task, task);
8811
+ this.navigationSettingsChain = run.then(() => undefined, () => undefined);
8812
+ return run;
8813
+ }
8067
8814
  //#endregion
8068
8815
  //#region ---- Tab Management ----
8069
8816
  async ensureHomeTab() {
@@ -8105,7 +8852,7 @@ class AXPTabNavigationService {
8105
8852
  }
8106
8853
  this.tabs.update((items) => {
8107
8854
  const withoutDuplicate = items.filter((item) => {
8108
- if (item.key === tab.key) {
8855
+ if (item.key === tab.key || this.isSameEntityListTab(item, tab)) {
8109
8856
  return false;
8110
8857
  }
8111
8858
  return axpResolveTabKey(this.router, item.url) !== tab.key;
@@ -8165,14 +8912,29 @@ class AXPTabNavigationService {
8165
8912
  lastActivatedAt: now,
8166
8913
  };
8167
8914
  }
8168
- /** Finds a tab by scoped key or legacy full-path key for the same URL. */
8915
+ /** Finds a tab by scoped key, or by entity-list identity when an older key drifted. */
8169
8916
  findTabForUrl(url) {
8170
8917
  if (this.isHomePageUrl(url)) {
8171
8918
  return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
8172
8919
  }
8173
8920
  const resolvedKey = axpResolveTabKey(this.router, url);
8174
- return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
8921
+ const byKey = this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
8175
8922
  (tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
8923
+ if (byKey) {
8924
+ return byKey;
8925
+ }
8926
+ if (!axpIsEntityListPath(url)) {
8927
+ return undefined;
8928
+ }
8929
+ const identity = axpBuildTabKey(normalizeRouterUrl(url));
8930
+ return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY && axpIsEntityListPath(tab.url) && axpBuildTabKey(tab.url) === identity);
8931
+ }
8932
+ /** Same list menu (`mode`, `list`, …). Page-state query params do not make a new tab. */
8933
+ isSameEntityListTab(left, right) {
8934
+ if (!axpIsEntityListPath(left.url) || !axpIsEntityListPath(right.url)) {
8935
+ return false;
8936
+ }
8937
+ return axpBuildTabKey(left.url) === axpBuildTabKey(right.url);
8176
8938
  }
8177
8939
  /** True when the URL is the configured application home page (e.g. dashboard). */
8178
8940
  isHomePageUrl(url) {
@@ -8317,8 +9079,12 @@ class AXPTabNavigationService {
8317
9079
  return `${SESSION_STORAGE_PREFIX}(${userId})`;
8318
9080
  }
8319
9081
  persistSession() {
8320
- const key = this.sessionStorageKey();
8321
- if (!key || this.effectiveMode() === 'simple') {
9082
+ const key = this.ownedStorageKey;
9083
+ if (!key ||
9084
+ key !== this.sessionStorageKey() ||
9085
+ this.tabsOwnerStamp !== this.sessionStamp() ||
9086
+ !this.sessionTabsHydrated ||
9087
+ this.effectiveMode() === 'simple') {
8322
9088
  return;
8323
9089
  }
8324
9090
  const payload = {
@@ -8335,31 +9101,54 @@ class AXPTabNavigationService {
8335
9101
  this.tabs.set([]);
8336
9102
  this.activeTabKey.set(null);
8337
9103
  this.tabPreviewService.clearAll();
9104
+ this.routeCacheRegistry.clear();
8338
9105
  }
8339
- async restoreSessionIfNeeded() {
9106
+ async restoreSessionIfNeeded(context) {
9107
+ if (!this.isCurrentSessionSync(context)) {
9108
+ return;
9109
+ }
8340
9110
  if (this.effectiveMode() === 'simple') {
9111
+ this.sessionTabsHydrated = true;
8341
9112
  return;
8342
9113
  }
8343
9114
  if (axpIsCommandSubFlowNavigationSuspended()) {
9115
+ this.restoreDeferred = true;
8344
9116
  return;
8345
9117
  }
8346
- const storageKey = this.sessionStorageKey();
9118
+ const storageKey = context.storageKey;
8347
9119
  if (!storageKey) {
8348
- await this.ensureHomeTab();
8349
- await this.syncCurrentRouteAsTab();
9120
+ this.tabs.set([]);
9121
+ this.activeTabKey.set(null);
9122
+ this.tabPreviewService.clearAll();
9123
+ this.sessionTabsHydrated = true;
8350
9124
  return;
8351
9125
  }
8352
9126
  const raw = sessionStorage.getItem(storageKey);
8353
9127
  if (!raw) {
9128
+ this.tabs.set([]);
9129
+ this.activeTabKey.set(null);
9130
+ this.sessionTabsHydrated = true;
8354
9131
  await this.ensureHomeTab();
9132
+ if (!this.isCurrentSessionSync(context)) {
9133
+ return;
9134
+ }
8355
9135
  await this.syncCurrentRouteAsTab();
8356
9136
  return;
8357
9137
  }
8358
9138
  try {
8359
9139
  const parsed = JSON.parse(raw);
8360
- const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
9140
+ const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
9141
+ .filter((tab) => tab?.key && tab?.url)
9142
+ .map((tab) => ({ ...tab, url: axpInTabErrorRetryUrl(tab.url) }))));
9143
+ if (!this.isCurrentSessionSync(context)) {
9144
+ return;
9145
+ }
9146
+ this.sessionTabsHydrated = true;
8361
9147
  this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
8362
9148
  await this.ensureHomeTab();
9149
+ if (!this.isCurrentSessionSync(context)) {
9150
+ return;
9151
+ }
8363
9152
  const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
8364
9153
  this.activeTabKey.set(activeKey);
8365
9154
  const activeTab = this.tabs().find((tab) => tab.key === activeKey);
@@ -8367,17 +9156,18 @@ class AXPTabNavigationService {
8367
9156
  if (activeTab &&
8368
9157
  currentUrl !== normalizeRouterUrl(activeTab.url) &&
8369
9158
  axpIsTabEligibleUrl(currentUrl)) {
8370
- this.suppressNavigationSync = true;
8371
- try {
8372
- await this.router.navigateByUrl(activeTab.url);
8373
- }
8374
- finally {
8375
- this.suppressNavigationSync = false;
8376
- }
9159
+ await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(activeTab.url));
8377
9160
  }
8378
9161
  }
8379
9162
  catch {
9163
+ if (!this.isCurrentSessionSync(context)) {
9164
+ return;
9165
+ }
9166
+ this.sessionTabsHydrated = true;
8380
9167
  await this.ensureHomeTab();
9168
+ if (!this.isCurrentSessionSync(context)) {
9169
+ return;
9170
+ }
8381
9171
  await this.syncCurrentRouteAsTab();
8382
9172
  }
8383
9173
  }
@@ -8627,7 +9417,12 @@ class AXPLocalEntityOutboxService {
8627
9417
  this.handlers.delete(entityKey);
8628
9418
  }
8629
9419
  pendingFor(entityKey) {
8630
- return this.pendingOps().filter((op) => op.entityKey === entityKey);
9420
+ const userKey = this.resolveUserKey();
9421
+ return this.pendingOps().filter((op) => op.entityKey === entityKey && op.userKey === userKey);
9422
+ }
9423
+ /** Await durable operations before a feature overlays pending rows after reload. */
9424
+ hydrate() {
9425
+ return this.refreshPending();
8631
9426
  }
8632
9427
  async enqueue(input) {
8633
9428
  const userKey = this.resolveUserKey();
@@ -8638,10 +9433,7 @@ class AXPLocalEntityOutboxService {
8638
9433
  }
8639
9434
  const recordIds = [...new Set((input.recordIds ?? []).map((id) => id?.trim()).filter(Boolean))];
8640
9435
  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));
9436
+ const duplicate = existing.find((op) => op.entityKey === entityKey && op.action === action && sameIdSet(op.recordIds, recordIds) && stablePayload(op.payload) === stablePayload(input.payload));
8645
9437
  if (duplicate) {
8646
9438
  return;
8647
9439
  }
@@ -8652,7 +9444,8 @@ class AXPLocalEntityOutboxService {
8652
9444
  action,
8653
9445
  recordIds,
8654
9446
  payload: input.payload,
8655
- queuedAt: new Date().toISOString(),
9447
+ // Keep dependent operations ordered even if send and edit enqueue in the same millisecond.
9448
+ queuedAt: new Date(Math.max(Date.now(), ...existing.map((op) => Date.parse(op.queuedAt) + 1))).toISOString(),
8656
9449
  attempts: 0,
8657
9450
  lastError: null,
8658
9451
  };
@@ -8690,6 +9483,10 @@ class AXPLocalEntityOutboxService {
8690
9483
  }
8691
9484
  const ops = (await this.loadUserOps(userKey)).sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
8692
9485
  for (const op of ops) {
9486
+ // A queued batch must never continue under another account after an awaited HTTP call.
9487
+ if (userKey !== this.resolveUserKey() || !this.sessionService.isReadyForAppScopedApi) {
9488
+ break;
9489
+ }
8693
9490
  const handler = this.handlers.get(op.entityKey);
8694
9491
  if (!handler) {
8695
9492
  continue;
@@ -8745,6 +9542,8 @@ class AXPLocalEntityOutboxService {
8745
9542
  return;
8746
9543
  }
8747
9544
  const ops = await this.loadUserOps(userKey);
9545
+ if (userKey !== this.resolveUserKey())
9546
+ return;
8748
9547
  ops.sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
8749
9548
  this.pendingOps.set(ops);
8750
9549
  }
@@ -9268,6 +10067,23 @@ class AXPNotFoundError extends Error {
9268
10067
  }
9269
10068
  }
9270
10069
 
10070
+ /**
10071
+ * Placeholder for a wildcard route whose guard always redirects into the shell.
10072
+ * The component is not shown.
10073
+ */
10074
+ class AXPUnreachableRouteComponent {
10075
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10076
+ 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 }); }
10077
+ }
10078
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, decorators: [{
10079
+ type: Component,
10080
+ args: [{
10081
+ selector: 'axp-unreachable-route',
10082
+ template: '',
10083
+ changeDetection: ChangeDetectionStrategy.OnPush,
10084
+ }]
10085
+ }] });
10086
+
9271
10087
  /** Canonical route path for the platform 404 page. */
9272
10088
  const AXP_NOT_FOUND_ROUTE = '/error/404';
9273
10089
  /**
@@ -9276,7 +10092,8 @@ const AXP_NOT_FOUND_ROUTE = '/error/404';
9276
10092
  */
9277
10093
  const AXP_NOT_FOUND_CATCH_ALL_ROUTE = {
9278
10094
  path: '**',
9279
- redirectTo: AXP_NOT_FOUND_ROUTE,
10095
+ canActivate: [axpRedirectUnknownToNotFound],
10096
+ component: AXPUnreachableRouteComponent,
9280
10097
  };
9281
10098
  /**
9282
10099
  * Registers the platform catch-all 404 route.
@@ -9300,8 +10117,8 @@ function axpIsEntityRecordNotFound(data) {
9300
10117
  /**
9301
10118
  * Creates an Angular redirect command to the platform 404 page.
9302
10119
  */
9303
- function axpRedirectToNotFound(router = inject(Router)) {
9304
- return new RedirectCommand(router.parseUrl(AXP_NOT_FOUND_ROUTE));
10120
+ function axpRedirectToNotFound(router = inject(Router), sourceUrl) {
10121
+ return new RedirectCommand(axpInTabErrorUrlTree(router, '404', sourceUrl ?? router.url));
9305
10122
  }
9306
10123
 
9307
10124
  /** Canonical route path for the platform 401 page. */
@@ -9309,8 +10126,8 @@ const AXP_UNAUTHORIZED_ROUTE = '/error/401';
9309
10126
  /**
9310
10127
  * Creates an Angular redirect command to the platform 401 page.
9311
10128
  */
9312
- function axpRedirectToUnauthorized(router = inject(Router)) {
9313
- return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
10129
+ function axpRedirectToUnauthorized(router = inject(Router), sourceUrl) {
10130
+ return new RedirectCommand(axpInTabErrorUrlTree(router, '401', sourceUrl ?? router.url));
9314
10131
  }
9315
10132
 
9316
10133
  //#endregion
@@ -10078,15 +10895,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10078
10895
  const AXP_ENTITY_VIEW_ACCESS = new InjectionToken('AXP_ENTITY_VIEW_ACCESS');
10079
10896
 
10080
10897
  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
10898
  /**
10091
10899
 
10092
10900
  * Blocks navigation when the target route is not accessible for the current user.
@@ -10114,10 +10922,7 @@ const AXPMenuRouteGuard = (route, state) => {
10114
10922
  if (isAllowed instanceof UrlTree) {
10115
10923
  return isAllowed;
10116
10924
  }
10117
- if (axpShouldStayOnCurrentPage(router, state.url)) {
10118
- return false;
10119
- }
10120
- return router.parseUrl(AXP_UNAUTHORIZED_ROUTE);
10925
+ return axpInTabErrorUrlTree(router, '401', state.url);
10121
10926
  }));
10122
10927
  };
10123
10928
 
@@ -11175,7 +11980,7 @@ class AXPCommonModule {
11175
11980
  provide: AXP_SETTING_DEFINITION_PROVIDER,
11176
11981
  useFactory: async () => {
11177
11982
  const injector = inject(Injector);
11178
- const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs')).AXPCommonSettingProvider;
11983
+ const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
11179
11984
  return new provider(injector);
11180
11985
  },
11181
11986
  multi: true,
@@ -11298,7 +12103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11298
12103
  provide: AXP_SETTING_DEFINITION_PROVIDER,
11299
12104
  useFactory: async () => {
11300
12105
  const injector = inject(Injector);
11301
- const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs')).AXPCommonSettingProvider;
12106
+ const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
11302
12107
  return new provider(injector);
11303
12108
  },
11304
12109
  multi: true,
@@ -11343,20 +12148,38 @@ const AXP_ROOT_CONFIG_TOKEN = new InjectionToken('AXP_ROOT_CONFIG_TOKEN');
11343
12148
 
11344
12149
  /**
11345
12150
  * 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.
12151
+ * `AXFileService.choose()` only listens for `change`, so dismissing the dialog leaves that promise pending.
11347
12152
  */
11348
12153
  function chooseSystemFiles(options = {}) {
11349
- const document = inject(DOCUMENT);
12154
+ return openSystemFileDialog(inject(DOCUMENT), options);
12155
+ }
12156
+ /**
12157
+ * Same picker as {@link chooseSystemFiles}, with the document passed in so callers can `await` before opening it.
12158
+ */
12159
+ function openSystemFileDialog(doc, options = {}) {
12160
+ const view = doc.defaultView;
12161
+ if (!view) {
12162
+ return Promise.resolve([]);
12163
+ }
11350
12164
  const accept = options.accept ?? '';
11351
12165
  const multiple = options.multiple ?? false;
11352
12166
  return new Promise((resolve) => {
11353
- const input = document.createElement('input');
12167
+ const input = doc.createElement('input');
11354
12168
  input.type = 'file';
11355
12169
  input.accept = accept;
11356
12170
  input.multiple = multiple;
11357
12171
  input.style.display = 'none';
11358
- document.body.appendChild(input);
12172
+ doc.body.appendChild(input);
11359
12173
  let settled = false;
12174
+ let dialogOpened = false;
12175
+ let watchFocus = false;
12176
+ const cleanup = () => {
12177
+ input.removeEventListener('change', onChange);
12178
+ input.removeEventListener('cancel', onCancel);
12179
+ view.removeEventListener('blur', onBlur);
12180
+ view.removeEventListener('focus', onFocus);
12181
+ input.remove();
12182
+ };
11360
12183
  const settle = (files) => {
11361
12184
  if (settled) {
11362
12185
  return;
@@ -11365,21 +12188,37 @@ function chooseSystemFiles(options = {}) {
11365
12188
  cleanup();
11366
12189
  resolve(files);
11367
12190
  };
11368
- const cleanup = () => {
11369
- input.removeEventListener('change', onChange);
11370
- window.removeEventListener('focus', onWindowFocus);
11371
- input.remove();
11372
- };
11373
12191
  const onChange = () => {
11374
12192
  settle(Array.from(input.files ?? []));
11375
12193
  };
11376
- const onWindowFocus = () => {
11377
- // Allow `change` to fire first when the user confirms a selection.
11378
- window.setTimeout(() => settle([]), 300);
12194
+ const onCancel = () => {
12195
+ settle([]);
12196
+ };
12197
+ const onBlur = () => {
12198
+ if (watchFocus) {
12199
+ dialogOpened = true;
12200
+ }
12201
+ };
12202
+ const onFocus = () => {
12203
+ if (!watchFocus || !dialogOpened) {
12204
+ return;
12205
+ }
12206
+ // `change` fires before this timeout when the user confirms a file.
12207
+ view.setTimeout(() => {
12208
+ if (!settled) {
12209
+ settle(Array.from(input.files ?? []));
12210
+ }
12211
+ }, 300);
11379
12212
  };
11380
12213
  input.addEventListener('change', onChange);
11381
- window.addEventListener('focus', onWindowFocus, { once: true });
12214
+ input.addEventListener('cancel', onCancel);
12215
+ view.addEventListener('blur', onBlur);
12216
+ view.addEventListener('focus', onFocus);
11382
12217
  input.click();
12218
+ // Ignore blur/focus caused by opening the dialog itself. Cancel still settles via the `cancel` event.
12219
+ view.setTimeout(() => {
12220
+ watchFocus = true;
12221
+ }, 0);
11383
12222
  });
11384
12223
  }
11385
12224
  //#endregion
@@ -12213,8 +13052,11 @@ class AXPLocalEntityEngine {
12213
13052
  return bucket.syncInFlight;
12214
13053
  }
12215
13054
  bucket.syncInFlight = (async () => {
13055
+ const userKey = this.resolveUserKey();
12216
13056
  const result = await fetchHot();
12217
- await this.replaceAll(entityKey, result.items ?? []);
13057
+ if (userKey === this.resolveUserKey()) {
13058
+ await this.replaceAll(entityKey, result.items ?? []);
13059
+ }
12218
13060
  })().finally(() => {
12219
13061
  bucket.syncInFlight = null;
12220
13062
  });
@@ -12289,6 +13131,8 @@ class AXPLocalEntityEngine {
12289
13131
  await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, async () => {
12290
13132
  await axpLocalEntityIndexedDb.items.bulkDelete(storageIds);
12291
13133
  });
13134
+ if (userKey !== this.resolveUserKey())
13135
+ return;
12292
13136
  bucket.items.set(next);
12293
13137
  bucket.hydrated.set(true);
12294
13138
  this.publish({ type: 'store-changed', entityKey, userKey, originId: this.originId });
@@ -12347,10 +13191,9 @@ class AXPLocalEntityEngine {
12347
13191
  bucket.hydrated.set(true);
12348
13192
  return;
12349
13193
  }
12350
- const stored = await axpLocalEntityIndexedDb.items
12351
- .where('[entityKey+userKey]')
12352
- .equals([entityKey, userKey])
12353
- .toArray();
13194
+ const stored = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).toArray();
13195
+ if (userKey !== this.resolveUserKey())
13196
+ return;
12354
13197
  const items = this.prune(entityKey, stored.map((row) => row.payload));
12355
13198
  this.userKeySignal.set(userKey);
12356
13199
  bucket.items.set(items);
@@ -12373,10 +13216,7 @@ class AXPLocalEntityEngine {
12373
13216
  const keepIds = new Set(stored.map((row) => row.storageId));
12374
13217
  const metaId = buildLocalEntityMetaId(entityKey, userKey);
12375
13218
  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();
13219
+ const existing = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).primaryKeys();
12380
13220
  const staleIds = existing.filter((id) => !keepIds.has(String(id)));
12381
13221
  if (staleIds.length > 0) {
12382
13222
  await axpLocalEntityIndexedDb.items.bulkDelete(staleIds);
@@ -12391,6 +13231,8 @@ class AXPLocalEntityEngine {
12391
13231
  lastSyncedAt: new Date().toISOString(),
12392
13232
  });
12393
13233
  });
13234
+ if (userKey !== this.resolveUserKey())
13235
+ return;
12394
13236
  this.userKeySignal.set(userKey);
12395
13237
  bucket.items.set(items);
12396
13238
  bucket.hydrated.set(true);
@@ -12412,6 +13254,8 @@ class AXPLocalEntityEngine {
12412
13254
  }
12413
13255
  this.userKeySignal.set(userKey);
12414
13256
  for (const [entityKey, bucket] of this.buckets) {
13257
+ bucket.items.set([]);
13258
+ bucket.hydrated.set(false);
12415
13259
  bucket.bootstrapPromise = null;
12416
13260
  bucket.hydratePromise = null;
12417
13261
  bucket.syncInFlight = null;
@@ -14054,5 +14898,5 @@ const axpPageGetContextQueryDefinition = defineQuery({
14054
14898
  * Generated bundle index. Do not edit.
14055
14899
  */
14056
14900
 
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 };
14901
+ 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
14902
  //# sourceMappingURL=acorex-platform-framework-client-common.mjs.map