@acorex/platform 21.0.0-next.71 → 21.0.0-next.73

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 (60) hide show
  1. package/contracts/README.md +3 -0
  2. package/fesm2022/acorex-platform-auth.mjs +82 -60
  3. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  4. package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
  5. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  6. package/fesm2022/acorex-platform-common.mjs +3796 -1677
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-contracts.mjs +10 -0
  9. package/fesm2022/acorex-platform-contracts.mjs.map +1 -0
  10. package/fesm2022/acorex-platform-core.mjs +1126 -114
  11. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-builder.mjs +56 -170
  13. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-components.mjs +254 -53
  15. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
  17. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  18. package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
  19. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
  20. package/fesm2022/acorex-platform-layout-entity.mjs +403 -460
  21. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-views.mjs +675 -301
  23. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs +126 -75
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  26. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs} +2 -2
  27. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map} +1 -1
  28. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs} +2 -2
  29. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map} +1 -1
  30. package/fesm2022/acorex-platform-layout-widgets.mjs +450 -750
  31. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  32. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  33. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  34. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  35. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  36. package/fesm2022/acorex-platform-themes-default.mjs +79 -35
  37. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  38. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +1 -1
  39. package/fesm2022/acorex-platform-themes-shared.mjs +3 -0
  40. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  41. package/fesm2022/acorex-platform-workflow.mjs +57 -54
  42. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  43. package/package.json +5 -1
  44. package/types/acorex-platform-auth.d.ts +31 -22
  45. package/types/acorex-platform-common.d.ts +892 -259
  46. package/types/acorex-platform-contracts.d.ts +39 -0
  47. package/types/acorex-platform-core.d.ts +291 -47
  48. package/types/acorex-platform-layout-builder.d.ts +10 -22
  49. package/types/acorex-platform-layout-components.d.ts +86 -14
  50. package/types/acorex-platform-layout-entity.d.ts +218 -212
  51. package/types/acorex-platform-layout-views.d.ts +125 -67
  52. package/types/acorex-platform-layout-widget-core.d.ts +56 -62
  53. package/types/acorex-platform-layout-widgets.d.ts +78 -65
  54. package/types/acorex-platform-themes-default.d.ts +14 -4
  55. package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
  56. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
  57. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
  58. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
  59. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
  60. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
@@ -2,7 +2,7 @@ import { AXToastService } from '@acorex/components/toast';
2
2
  import * as i6 from '@acorex/core/translation';
3
3
  import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString, translateSync } from '@acorex/core/translation';
4
4
  import * as i4$3 from '@acorex/platform/common';
5
- import { AXPEntityCommandScope, AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, AXPFileActionsService, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
5
+ import { AXPNotFoundError, AXPEntityCommandScope, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, normalizeEntityDisplayTemplate, isUnresolvedEntityDisplayTemplate, AXPFilterOperatorMiddlewareService, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPRefreshEvent, AXPReloadEvent, axpRedirectToNotFound, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, buildEntitySearchTitleContext, resolveEntityRowTitleTemplate, AXPFileActionsService, axpIsEntityRecordNotFound, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
6
6
  import * as i0 from '@angular/core';
7
7
  import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, computed, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, NgModule, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, makeEnvironmentProviders } from '@angular/core';
8
8
  import { Subject, takeUntil } from 'rxjs';
@@ -12,7 +12,7 @@ import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistr
12
12
  import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPHookService, AXPDataGenerator, AXPComponentSlotModule, AXPContextStore, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
13
13
  import { cloneDeep, merge, get, castArray, set, orderBy, omit, isNil, isEmpty, isEqual as isEqual$1, isArray, isString } from 'lodash-es';
14
14
  import { transform, isEqual } from 'lodash';
15
- import { AXPSessionService, AXPAuthGuard, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
15
+ import { AXPSessionService, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
16
16
  import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
17
17
  import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, AXPCommandExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
18
18
  import * as i1 from '@acorex/components/button';
@@ -25,7 +25,7 @@ import { AXFormatService } from '@acorex/core/format';
25
25
  import * as i5 from '@angular/common';
26
26
  import { CommonModule, AsyncPipe, NgClass } from '@angular/common';
27
27
  import { AXPThemeLayoutBlockComponent, AXPPreloadFiltersComponent, AXP_PAGE_COMPONENT_PROVIDER, AXPStateMessageComponent, AXPColumnItemListComponent, AXPDataSelectorService, AXPPageComponentRegistryService } from '@acorex/platform/layout/components';
28
- import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageComponentRendererDirective, AXPPageComponentInstanceRegistryService } from '@acorex/platform/layout/views';
28
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageComponentRendererDirective, AXPPageComponentInstanceRegistryService, axpDetailsViewCanDeactivateGuard } from '@acorex/platform/layout/views';
29
29
  import { AXDataSource } from '@acorex/cdk/common';
30
30
  import { AXP_ENTITY_CRUD_SETUP } from '@acorex/platform/domain';
31
31
  export { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
@@ -823,7 +823,7 @@ class AXPEntityDefinitionRegistryService {
823
823
  throw error; // Rethrow to allow error handling by caller
824
824
  }
825
825
  if (!config) {
826
- throw new Error(`Invalid entity name: ${key}`);
826
+ throw new AXPNotFoundError(`Invalid entity name: ${key}`);
827
827
  }
828
828
  }
829
829
  return config;
@@ -902,6 +902,7 @@ function entityMasterDeleteAction() {
902
902
  type: AXPSystemActionType.Delete,
903
903
  scope: AXPEntityCommandScope.Individual,
904
904
  order: 100,
905
+ shortcuts: ['ctrl+shift+delete'],
905
906
  };
906
907
  }
907
908
  function entityMasterCrudActions(options) {
@@ -2498,8 +2499,7 @@ class AXPOpenEntityDetailsCommand {
2498
2499
  };
2499
2500
  }
2500
2501
  const url = `/${this.sessionService.application?.name}/m/${module}/e/${entityName}/${data.id}/view`;
2501
- // Navigate to the entity details page
2502
- this.router.navigate([url]);
2502
+ await axpNavigateAppPath(this.router, url);
2503
2503
  return {
2504
2504
  success: true,
2505
2505
  };
@@ -2971,18 +2971,56 @@ var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
2971
2971
  AXPViewEntityDetailsCommand: AXPViewEntityDetailsCommand
2972
2972
  });
2973
2973
 
2974
- //#region ---- Template resolution ----
2975
- /**
2976
- * Normalizes lookup/search display templates for row-based formatting.
2977
- * Converts `context.eval('path')` expressions to `{{ path }}` and single braces to mustache form.
2978
- */
2979
- function normalizeLookupDisplayTemplate(template) {
2980
- const withContextEval = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ $1 }}');
2981
- if (withContextEval.includes('{{')) {
2982
- return withContextEval;
2974
+ //#region ---- Datasource keys ----
2975
+ /** Platform entity layout datasource registry names. */
2976
+ const AXPEntityDataSourceKeys = {
2977
+ Entities: 'entities',
2978
+ };
2979
+ //#endregion
2980
+
2981
+ const AXPEntityEventsKeys = {
2982
+ REFRESH_LAYOUT: 'entity:refresh-layout',
2983
+ REFRESH_DATA: 'entity:refresh-data',
2984
+ };
2985
+
2986
+ //#endregion
2987
+
2988
+ /** Entity query filter field for {@link AXPRecordWorkflowInfo.instanceId}. */
2989
+ const AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD = 'workflowInfo.instanceId';
2990
+ /** Entity query filter field for {@link AXPRecordWorkflowInfo.correlationId}. */
2991
+ const AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD = 'workflowInfo.correlationId';
2992
+ /** Entity query filter field for {@link AXPRecordWorkflowInfo.definitionId}. */
2993
+ const AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD = 'workflowInfo.definitionId';
2994
+ /** Returns a trimmed workflow instance id from a record, if present. */
2995
+ function getRecordWorkflowInstanceId(record) {
2996
+ const id = record?.workflowInfo?.instanceId?.trim();
2997
+ return id && id.length > 0 ? id : null;
2998
+ }
2999
+ /** Returns a trimmed workflow correlation id from a record, if present. */
3000
+ function getRecordWorkflowCorrelationId(record) {
3001
+ const id = record?.workflowInfo?.correlationId?.trim();
3002
+ return id && id.length > 0 ? id : null;
3003
+ }
3004
+ /** Builds canonical workflow metadata for a domain record (engine / middleware). */
3005
+ function buildAXPRecordWorkflowInfo(params) {
3006
+ const instanceId = params.instanceId?.trim();
3007
+ if (!instanceId) {
3008
+ return {};
2983
3009
  }
2984
- return withContextEval.replace(/\{/g, '{{').replace(/\}/g, '}}');
3010
+ const definitionId = params.definitionId?.trim();
3011
+ const correlationId = params.correlationId?.trim();
3012
+ return {
3013
+ instanceId,
3014
+ ...(definitionId ? { definitionId } : { definitionId: null }),
3015
+ ...(correlationId ? { correlationId } : { correlationId: null }),
3016
+ ...(params.pinnedVersion != null ? { pinnedVersion: params.pinnedVersion } : {}),
3017
+ };
2985
3018
  }
3019
+
3020
+ //#region ---- Contracts barrel ----
3021
+ //#endregion
3022
+
3023
+ //#region ---- Template resolution ----
2986
3024
  /**
2987
3025
  * Resolves the display template for a lookup item.
2988
3026
  * Priority: explicit `displayFormat` → entity `formats.lookup` (template) → entity `formats.searchResult.title`.
@@ -2991,18 +3029,18 @@ function normalizeLookupDisplayTemplate(template) {
2991
3029
  function resolveLookupDisplayTemplate(entity, options) {
2992
3030
  const explicit = options.displayFormat?.trim();
2993
3031
  if (explicit) {
2994
- return normalizeLookupDisplayTemplate(explicit);
3032
+ return normalizeEntityDisplayTemplate(explicit);
2995
3033
  }
2996
3034
  if (options.textField?.trim()) {
2997
3035
  return undefined;
2998
3036
  }
2999
3037
  const lookupFormat = entity?.formats?.lookup?.trim();
3000
3038
  if (lookupFormat && (lookupFormat.includes('{{') || lookupFormat.includes('{'))) {
3001
- return normalizeLookupDisplayTemplate(lookupFormat);
3039
+ return normalizeEntityDisplayTemplate(lookupFormat);
3002
3040
  }
3003
3041
  const searchTitle = entity?.formats?.searchResult?.title?.trim();
3004
3042
  if (searchTitle) {
3005
- return normalizeLookupDisplayTemplate(searchTitle);
3043
+ return normalizeEntityDisplayTemplate(searchTitle);
3006
3044
  }
3007
3045
  return undefined;
3008
3046
  }
@@ -3043,7 +3081,7 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
3043
3081
  const formatted = formatService.format(template, 'string', item);
3044
3082
  if (formatted) {
3045
3083
  const text = String(formatted);
3046
- if (!isUnresolvedLookupDisplayTemplate(text)) {
3084
+ if (!isUnresolvedEntityDisplayTemplate(text)) {
3047
3085
  return text;
3048
3086
  }
3049
3087
  }
@@ -3064,10 +3102,6 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
3064
3102
  }
3065
3103
  return String(raw);
3066
3104
  }
3067
- /** True when a formatted label still contains unresolved template markers. */
3068
- function isUnresolvedLookupDisplayTemplate(value) {
3069
- return /\{\{/.test(value) || /context\.eval\s*\(/.test(value);
3070
- }
3071
3105
  //#endregion
3072
3106
 
3073
3107
  class AXPEntityDetailPopoverComponent {
@@ -3107,7 +3141,7 @@ class AXPEntityDetailPopoverComponent {
3107
3141
  }
3108
3142
  }
3109
3143
  const preset = this.displayTitle()?.trim();
3110
- if (preset && !isUnresolvedLookupDisplayTemplate(preset)) {
3144
+ if (preset && !isUnresolvedEntityDisplayTemplate(preset)) {
3111
3145
  return preset;
3112
3146
  }
3113
3147
  const fallbackItem = this.item();
@@ -3206,7 +3240,7 @@ class AXPEntityDetailPopoverComponent {
3206
3240
  resolveLookupDisplayLabel(item, entityDefinition) {
3207
3241
  const label = formatLookupItemDisplay(item, entityDefinition, { textField: this.textField() }, this.formatService, (value) => this.translation.resolve(value));
3208
3242
  const resolved = typeof label === 'string' ? label : this.translation.resolve(label);
3209
- return resolved && !isUnresolvedLookupDisplayTemplate(resolved) ? resolved : '';
3243
+ return resolved && !isUnresolvedEntityDisplayTemplate(resolved) ? resolved : '';
3210
3244
  }
3211
3245
  /**
3212
3246
  * Returns true if a value is meaningful for display (non-empty/non-null).
@@ -3777,6 +3811,16 @@ class EntityDataAccessor {
3777
3811
  }
3778
3812
  return await this.crudService.query(queryRequest);
3779
3813
  }
3814
+ /**
3815
+ * Queries category entities linked to this entity (`{module}.{entity}Category`).
3816
+ * Falls back to CRUD category storage (same as {@link AXMEntityCrudServiceImpl.getCategoriesList}).
3817
+ */
3818
+ async getCategoriesList(request = { skip: 0, take: 1000 }) {
3819
+ if (!this.crudService) {
3820
+ this.crudService = new AXMEntityCrudServiceImpl(`${this.module}.${this.entity}`);
3821
+ }
3822
+ return await this.crudService.getCategoriesList(request);
3823
+ }
3780
3824
  /**
3781
3825
  * Creates a new entity.
3782
3826
  * @param data - The entity data to create
@@ -4135,7 +4179,7 @@ class AXPEntitiesListDataSourceDefinition {
4135
4179
  items() {
4136
4180
  return Promise.resolve([
4137
4181
  {
4138
- name: 'entities',
4182
+ name: AXPEntityDataSourceKeys.Entities,
4139
4183
  title: 'Entities',
4140
4184
  columns: [
4141
4185
  {
@@ -4356,6 +4400,7 @@ class AXPEntityCommandTriggerViewModel {
4356
4400
  this.hidden = action.hidden ?? false;
4357
4401
  this.disabled = action.disabled ?? false;
4358
4402
  this.default = action.default ?? false;
4403
+ this.shortcuts = action.shortcuts;
4359
4404
  this.scope = action.scope;
4360
4405
  this.order = action.order ?? 0;
4361
4406
  this.isChild = isChild;
@@ -4739,11 +4784,6 @@ class AXPEntityDetailListViewModel {
4739
4784
  }
4740
4785
  }
4741
4786
 
4742
- const AXPEntityEventsKeys = {
4743
- REFRESH_LAYOUT: 'entity:refresh-layout',
4744
- REFRESH_DATA: 'entity:refresh-data',
4745
- };
4746
-
4747
4787
  /**
4748
4788
  * Entity Event Dispatcher - A wrapper for entity-specific events
4749
4789
  * Handles pattern-based dispatching for entity operations with wildcard support
@@ -6642,20 +6682,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6642
6682
  type: Injectable,
6643
6683
  args: [{ providedIn: 'root' }]
6644
6684
  }] });
6645
- const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory)) => {
6685
+ const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory), router = inject(Router)) => {
6646
6686
  const moduleName = route.parent?.paramMap.get('module');
6647
6687
  const entityName = route.paramMap.get('entity');
6648
- const vm = await service.create(moduleName, entityName);
6649
- // Check if filters are provided in query params
6650
- const filtersParam = route.queryParamMap.get('filters');
6651
- if (filtersParam) {
6652
- const applied = vm.applyFiltersFromQueryParams(filtersParam);
6653
- if (applied) {
6654
- // Trigger filter and sort application
6655
- await vm.applyFilterAndSort();
6688
+ try {
6689
+ const vm = await service.create(moduleName, entityName);
6690
+ // Check if filters are provided in query params
6691
+ const filtersParam = route.queryParamMap.get('filters');
6692
+ if (filtersParam) {
6693
+ const applied = vm.applyFiltersFromQueryParams(filtersParam);
6694
+ if (applied) {
6695
+ // Trigger filter and sort application
6696
+ await vm.applyFilterAndSort();
6697
+ }
6656
6698
  }
6699
+ return vm;
6700
+ }
6701
+ catch (error) {
6702
+ if (error instanceof AXPNotFoundError) {
6703
+ return axpRedirectToNotFound(router);
6704
+ }
6705
+ throw error;
6657
6706
  }
6658
- return vm;
6659
6707
  };
6660
6708
 
6661
6709
  const AXPEntityDeletedEvent = createWorkFlowEvent('[Entity] Deleted');
@@ -6706,7 +6754,7 @@ class AXPEntityPerformDeleteAction extends AXPWorkflowAction {
6706
6754
  dialog = this.loadingDialog.show({
6707
6755
  title: await this.translationService.translateAsync('@general:workflow.deleting'),
6708
6756
  mode: 'determinate',
6709
- status: 'Deleting...',
6757
+ status: await this.translationService.translateAsync('@general:workflow.deleting'),
6710
6758
  progressValue: 0,
6711
6759
  text: `0/${idLength}`,
6712
6760
  buttons: [
@@ -7467,10 +7515,19 @@ class AXPEntityPreloadFiltersViewModel {
7467
7515
  const AXPEntityPreloadFiltersViewModelResolver = async (route, state) => {
7468
7516
  const injector = inject(Injector);
7469
7517
  const entityRegistry = inject(AXPEntityDefinitionRegistryService);
7518
+ const router = inject(Router);
7470
7519
  const moduleName = route.parent?.paramMap.get('module');
7471
7520
  const entityName = route.paramMap.get('entity');
7472
- const entity = await entityRegistry.resolve(moduleName, entityName);
7473
- return new AXPEntityPreloadFiltersViewModel(injector, entity);
7521
+ try {
7522
+ const entity = await entityRegistry.resolve(moduleName, entityName);
7523
+ return new AXPEntityPreloadFiltersViewModel(injector, entity);
7524
+ }
7525
+ catch (error) {
7526
+ if (error instanceof AXPNotFoundError) {
7527
+ return axpRedirectToNotFound(router);
7528
+ }
7529
+ throw error;
7530
+ }
7474
7531
  };
7475
7532
  //#endregion
7476
7533
 
@@ -7835,8 +7892,6 @@ async function runEntityQuery(entityName, request, adapters) {
7835
7892
  }
7836
7893
  //#endregion
7837
7894
 
7838
- //#endregion
7839
-
7840
7895
  /** Component key used when display is 'page'. Register a page component with this key via AXP_PAGE_COMPONENT_PROVIDER. */
7841
7896
  const ATTACHMENTS_PAGE_COMPONENT_KEY = 'entity-attachments-page';
7842
7897
  /** Default section order for attachments when display is 'section' (after meta-data-form). */
@@ -8035,7 +8090,7 @@ class AXMAttachmentsPageComponentProvider {
8035
8090
  return [
8036
8091
  {
8037
8092
  key: ATTACHMENTS_PAGE_COMPONENT_KEY,
8038
- loader: () => import('./acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs').then((m) => m.AXMAttachmentsPageComponent),
8093
+ loader: () => import('./acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs').then((m) => m.AXMAttachmentsPageComponent),
8039
8094
  },
8040
8095
  ];
8041
8096
  }
@@ -8354,7 +8409,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8354
8409
  this.noRecordsTitle = signal('', ...(ngDevMode ? [{ debugName: "noRecordsTitle" }] : /* istanbul ignore next */ []));
8355
8410
  this.currentSearchTerm = null;
8356
8411
  this.isRefreshing = false;
8357
- this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations
8412
+ this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations (multiple mode)
8358
8413
  /**
8359
8414
  * Computed property to check if we should show the "no search results" empty state.
8360
8415
  * Returns true when search is active, not searching, and no results found.
@@ -8421,9 +8476,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8421
8476
  }
8422
8477
  });
8423
8478
  }
8424
- // Mark pre-selected nodes as selected in the child nodes (for visual display)
8425
- // ONLY do this during initial load, NOT during user selection changes
8426
- if (!this.isUpdatingSelection) {
8479
+ // Mark pre-selected leaf nodes on loaded children (multiple mode only; single uses controlledSelection)
8480
+ if (!this.isUpdatingSelection && this.allowMultiple()) {
8427
8481
  const selectedIds = this.selectedNodeIds();
8428
8482
  childNodes.forEach((node) => {
8429
8483
  const nodeId = String(node['id'] ?? '');
@@ -8431,38 +8485,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8431
8485
  node['selected'] = true;
8432
8486
  }
8433
8487
  });
8434
- // After children load, programmatically select pre-selected nodes in tree component
8435
- // This ensures the tree's internal state matches our selectedNodeIds
8436
- // Skip this if we're in the middle of a selection update (user is selecting/deselecting)
8437
- const treeComponent = this.tree();
8438
- if (treeComponent && selectedIds.length > 0) {
8439
- // Use setTimeout to ensure nodes are in tree structure before selecting
8440
- setTimeout(() => {
8441
- // Double-check we're not in a selection update when timeout fires
8442
- if (this.isUpdatingSelection) {
8443
- return;
8444
- }
8445
- this.isUpdatingSelection = true;
8446
- try {
8447
- // Re-check selectedNodeIds at this point (might have changed)
8448
- const currentSelectedIds = this.selectedNodeIds();
8449
- childNodes.forEach((node) => {
8450
- const nodeId = String(node['id'] ?? '');
8451
- if (nodeId && currentSelectedIds.includes(nodeId) && nodeId !== 'all') {
8452
- try {
8453
- treeComponent.selectNode(nodeId);
8454
- }
8455
- catch {
8456
- // Node might not be in tree yet
8457
- }
8458
- }
8459
- });
8460
- }
8461
- finally {
8462
- this.isUpdatingSelection = false;
8463
- }
8464
- }, 10);
8465
- }
8466
8488
  }
8467
8489
  return childNodes;
8468
8490
  };
@@ -8488,6 +8510,60 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8488
8510
  }
8489
8511
  //#endregion
8490
8512
  //#region ---- Private Methods ----
8513
+ /** Keeps at most one id when {@link allowMultiple} is false. */
8514
+ normalizeSelectedIds(ids) {
8515
+ const filtered = ids.filter((id) => id && id !== 'all');
8516
+ if (this.allowMultiple()) {
8517
+ return [...filtered];
8518
+ }
8519
+ return filtered.length > 0 ? [filtered[filtered.length - 1]] : [];
8520
+ }
8521
+ setSelectedNodeIds(ids) {
8522
+ this.selectedNodeIds.set(this.normalizeSelectedIds(ids));
8523
+ }
8524
+ /** Keeps widget selectedNodeIds in sync when the tree uses controlled single selection. */
8525
+ onControlledSelectedIdsChange(ids) {
8526
+ if (this.allowMultiple()) {
8527
+ return;
8528
+ }
8529
+ this.setSelectedNodeIds(ids);
8530
+ }
8531
+ async waitForTreeComponent(maxWaitMs = 3500) {
8532
+ const deadline = Date.now() + maxWaitMs;
8533
+ while (Date.now() < deadline) {
8534
+ const treeComponent = this.tree();
8535
+ if (treeComponent) {
8536
+ return treeComponent;
8537
+ }
8538
+ await new Promise((resolve) => setTimeout(resolve, 80));
8539
+ }
8540
+ return undefined;
8541
+ }
8542
+ /** Expands ancestor paths and reveals the initial selection via the tree API. */
8543
+ async revealInitialSelection(ids) {
8544
+ const normalizedIds = this.normalizeSelectedIds(ids);
8545
+ if (normalizedIds.length === 0) {
8546
+ return;
8547
+ }
8548
+ const treeComponent = await this.waitForTreeComponent();
8549
+ if (!treeComponent) {
8550
+ return;
8551
+ }
8552
+ try {
8553
+ if (this.allowMultiple()) {
8554
+ await this.restoreMultipleSelectionAfterReload(normalizedIds);
8555
+ }
8556
+ else {
8557
+ const chains = await this.buildAncestorChains(normalizedIds);
8558
+ const ancestorPaths = normalizedIds.map((id) => chains.get(id) ?? []);
8559
+ await treeComponent.selectAndReveal(normalizedIds, { ancestorPaths });
8560
+ }
8561
+ }
8562
+ catch (error) {
8563
+ console.error('Error revealing initial category selection:', error);
8564
+ }
8565
+ this.changeDetectorRef.markForCheck();
8566
+ }
8491
8567
  async initializeTree() {
8492
8568
  if (!this.entityKey()) {
8493
8569
  this.loading.set(false);
@@ -8512,12 +8588,12 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8512
8588
  // Resolve initial selected IDs from data (popup) or input binding
8513
8589
  const fromData = this.selectedValues().filter((id) => id && id !== 'all');
8514
8590
  const fromInput = (this.selectedValuesInput() ?? []).filter((id) => id && id !== 'all');
8515
- const initialSelectedIds = fromData.length > 0 ? fromData : fromInput;
8591
+ const initialSelectedIds = this.normalizeSelectedIds(fromData.length > 0 ? fromData : fromInput);
8516
8592
  if (initialSelectedIds.length > 0) {
8517
8593
  await this.loadMissingNodeData(initialSelectedIds);
8518
8594
  // Re-fetch any node that has no parent info (batch query may return minimal fields)
8519
8595
  await this.ensureParentDataInCache(initialSelectedIds);
8520
- this.selectedNodeIds.set(initialSelectedIds);
8596
+ this.setSelectedNodeIds(initialSelectedIds);
8521
8597
  }
8522
8598
  }
8523
8599
  catch (error) {
@@ -8531,52 +8607,20 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8531
8607
  const selectedIds = this.selectedNodeIds();
8532
8608
  if (selectedIds.length > 0) {
8533
8609
  this.initialExpandSyncDone = true;
8534
- this.runExpandAndSyncWhenTreeReady(selectedIds);
8610
+ void this.revealInitialSelection(selectedIds);
8535
8611
  }
8536
8612
  }
8537
8613
  /**
8538
8614
  * Called when popup data arrives after ngOnInit: load data, set selection, expand and sync.
8539
8615
  */
8540
8616
  runInitialExpandAndSync(ids) {
8541
- this.selectedNodeIds.set(ids);
8617
+ this.setSelectedNodeIds(ids);
8542
8618
  Promise.resolve()
8543
8619
  .then(() => this.loadMissingNodeData(ids))
8544
8620
  .then(() => this.ensureParentDataInCache(ids))
8545
- .then(() => {
8546
- this.runExpandAndSyncWhenTreeReady(ids);
8547
- })
8621
+ .then(() => this.revealInitialSelection(ids))
8548
8622
  .catch((err) => console.error('Error in runInitialExpandAndSync:', err));
8549
8623
  }
8550
- /**
8551
- * Runs expand path + sync selection once the tree viewChild is available.
8552
- * Uses retry loop so we don't run before the view has rendered the tree.
8553
- */
8554
- runExpandAndSyncWhenTreeReady(selectedIds) {
8555
- const maxWaitMs = 3500;
8556
- const pollMs = 80;
8557
- const start = Date.now();
8558
- const run = async () => {
8559
- while (Date.now() - start < maxWaitMs) {
8560
- const treeComponent = this.tree();
8561
- if (treeComponent) {
8562
- try {
8563
- await new Promise((resolve) => setTimeout(resolve, 320));
8564
- const ancestorChains = await this.buildAncestorChains(selectedIds);
8565
- await this.expandAncestorNodesInOrder(ancestorChains);
8566
- await new Promise((resolve) => setTimeout(resolve, 120));
8567
- await this.syncSelectionWithTree(selectedIds);
8568
- }
8569
- catch (error) {
8570
- console.error('Error syncing selection after tree render:', error);
8571
- }
8572
- this.changeDetectorRef.markForCheck();
8573
- return;
8574
- }
8575
- await new Promise((resolve) => setTimeout(resolve, pollMs));
8576
- }
8577
- };
8578
- run();
8579
- }
8580
8624
  //#endregion
8581
8625
  //#region ---- Public Methods ----
8582
8626
  /**
@@ -8981,8 +9025,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8981
9025
  this.nodesExpandedDuringSearch = [];
8982
9026
  return;
8983
9027
  }
8984
- // Store current selected IDs before reload
8985
- const selectedIds = this.selectedNodeIds();
9028
+ const expandTargetIds = [...this.selectedNodeIds()];
8986
9029
  // Reload tree to show all nodes (no filtering)
8987
9030
  await treeComponent.reloadData();
8988
9031
  // Collapse nodes that were expanded during search (in reverse order - leaves first)
@@ -9002,39 +9045,38 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9002
9045
  // Clear the stored expanded nodes
9003
9046
  this.expandedNodesBeforeSearch = [];
9004
9047
  // Restore selection state after tree reload
9005
- if (selectedIds.length > 0) {
9006
- // Wait for tree to stabilize after reload
9007
- await new Promise((resolve) => setTimeout(resolve, 100));
9008
- // Re-sync selection with tree
9009
- await this.restoreSelectionAfterReload(selectedIds);
9048
+ if (expandTargetIds.length > 0) {
9049
+ if (!this.allowMultiple()) {
9050
+ await this.revealInitialSelection(expandTargetIds);
9051
+ }
9052
+ else {
9053
+ await this.restoreMultipleSelectionAfterReload(expandTargetIds);
9054
+ }
9010
9055
  }
9011
9056
  }
9012
9057
  /**
9013
- * Restores selection state after tree reload.
9014
- * Expands ancestor nodes and selects the previously selected leaf nodes.
9058
+ * Restores multiple-mode leaf selection after search reset.
9015
9059
  */
9016
- async restoreSelectionAfterReload(selectedIds) {
9060
+ async restoreMultipleSelectionAfterReload(selectedLeafIds) {
9017
9061
  const treeComponent = this.tree();
9018
- if (!treeComponent || selectedIds.length === 0) {
9062
+ const normalizedIds = this.normalizeSelectedIds(selectedLeafIds);
9063
+ if (!treeComponent || normalizedIds.length === 0) {
9019
9064
  return;
9020
9065
  }
9021
9066
  this.isUpdatingSelection = true;
9022
9067
  try {
9023
- // Build ancestor chains for selected nodes
9024
- const ancestorChains = await this.buildAncestorChains(selectedIds);
9025
- // Expand ancestor nodes to make selected nodes visible
9068
+ const ancestorChains = await this.buildAncestorChains(normalizedIds);
9026
9069
  await this.expandAncestorNodesInOrder(ancestorChains);
9027
- // Wait for tree to render expanded nodes
9028
- await new Promise((resolve) => setTimeout(resolve, 50));
9029
- // Select the nodes visually in the tree
9030
- for (const id of selectedIds) {
9070
+ for (const id of normalizedIds) {
9031
9071
  try {
9032
9072
  treeComponent.selectNode(id);
9033
9073
  }
9034
9074
  catch {
9035
- // Node might not be in tree yet
9075
+ // Node might not be loaded yet
9036
9076
  }
9037
9077
  }
9078
+ await this.updateParentStatesAfterSelection(normalizedIds);
9079
+ treeComponent.refresh();
9038
9080
  }
9039
9081
  finally {
9040
9082
  this.isUpdatingSelection = false;
@@ -9051,10 +9093,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9051
9093
  if (!event.isUserInteraction) {
9052
9094
  return;
9053
9095
  }
9054
- // Don't process if we're already updating selection
9055
- if (this.isUpdatingSelection) {
9056
- return;
9057
- }
9058
9096
  // Cache node data for getSelectedItems (except for 'all' node)
9059
9097
  if (nodeId !== 'all') {
9060
9098
  const nodeData = node['data'];
@@ -9062,72 +9100,64 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9062
9100
  this.nodeDataCache.set(nodeId, nodeData);
9063
9101
  }
9064
9102
  }
9065
- // Check if this node is being selected or deselected
9103
+ // Single selection is handled by the tree controlledSelection + selectedIds binding.
9104
+ if (!this.allowMultiple()) {
9105
+ return;
9106
+ }
9066
9107
  const isSelected = node['selected'] === true;
9067
- if (this.allowMultiple()) {
9068
- const children = node['children'];
9069
- const childrenCount = node['childrenCount'];
9070
- // Determine if this node has children (is a parent node)
9071
- // A node has children if: childrenCount > 0, or has non-empty children array
9072
- // Note: childrenCount === undefined means "unknown" - we need to check via datasource
9073
- const hasLoadedChildren = children && children.length > 0;
9074
- const hasChildrenCount = childrenCount !== undefined && childrenCount > 0;
9075
- const isDefinitelyLeaf = childrenCount === 0 && (!children || children.length === 0);
9076
- if (isSelected) {
9077
- // SELECTION: Only add LEAF nodes to selectedNodeIds
9078
- this.isUpdatingSelection = true;
9079
- try {
9080
- if (nodeId === 'all') {
9081
- // "All Items" selected - recursively select all leaf descendants
9082
- await this.selectAllLeafDescendants(nodeId);
9083
- }
9084
- else if (isDefinitelyLeaf) {
9085
- // This is definitely a leaf node - add it directly
9108
+ const children = node['children'];
9109
+ const childrenCount = node['childrenCount'];
9110
+ // Determine if this node has children (is a parent node)
9111
+ // A node has children if: childrenCount > 0, or has non-empty children array
9112
+ // Note: childrenCount === undefined means "unknown" - we need to check via datasource
9113
+ const hasLoadedChildren = children && children.length > 0;
9114
+ const hasChildrenCount = childrenCount !== undefined && childrenCount > 0;
9115
+ const isDefinitelyLeaf = childrenCount === 0 && (!children || children.length === 0);
9116
+ if (isSelected) {
9117
+ // SELECTION: Only add LEAF nodes to selectedNodeIds
9118
+ this.isUpdatingSelection = true;
9119
+ try {
9120
+ if (nodeId === 'all') {
9121
+ // "All Items" selected - recursively select all leaf descendants
9122
+ await this.selectAllLeafDescendants(nodeId);
9123
+ }
9124
+ else if (isDefinitelyLeaf) {
9125
+ // This is definitely a leaf node - add it directly
9126
+ const currentSelected = new Set(this.selectedNodeIds());
9127
+ currentSelected.add(nodeId);
9128
+ this.setSelectedNodeIds(Array.from(currentSelected));
9129
+ }
9130
+ else if (hasLoadedChildren || hasChildrenCount) {
9131
+ // This node has children - only add its leaf descendants
9132
+ await this.selectAllLeafDescendants(nodeId);
9133
+ }
9134
+ else {
9135
+ // childrenCount is undefined - fetch once and reuse to avoid double datasource call
9136
+ const childNodes = await this.datasource(nodeId);
9137
+ if (!childNodes || childNodes.length === 0) {
9138
+ // No children - this is a leaf node, add it
9086
9139
  const currentSelected = new Set(this.selectedNodeIds());
9087
9140
  currentSelected.add(nodeId);
9088
- this.selectedNodeIds.set(Array.from(currentSelected));
9089
- }
9090
- else if (hasLoadedChildren || hasChildrenCount) {
9091
- // This node has children - only add its leaf descendants
9092
- await this.selectAllLeafDescendants(nodeId);
9141
+ this.setSelectedNodeIds(Array.from(currentSelected));
9093
9142
  }
9094
9143
  else {
9095
- // childrenCount is undefined - fetch once and reuse to avoid double datasource call
9096
- const childNodes = await this.datasource(nodeId);
9097
- if (!childNodes || childNodes.length === 0) {
9098
- // No children - this is a leaf node, add it
9099
- const currentSelected = new Set(this.selectedNodeIds());
9100
- currentSelected.add(nodeId);
9101
- this.selectedNodeIds.set(Array.from(currentSelected));
9102
- }
9103
- else {
9104
- // Has children - pass prefetched childNodes to avoid datasource(nodeId) again
9105
- await this.selectAllLeafDescendants(nodeId, childNodes);
9106
- }
9144
+ // Has children - pass prefetched childNodes to avoid datasource(nodeId) again
9145
+ await this.selectAllLeafDescendants(nodeId, childNodes);
9107
9146
  }
9108
9147
  }
9109
- finally {
9110
- this.isUpdatingSelection = false;
9111
- }
9112
9148
  }
9113
- else {
9114
- // DESELECTION: Remove node (if leaf) and all leaf descendants from selectedNodeIds
9115
- this.isUpdatingSelection = true;
9116
- try {
9117
- await this.deselectAllLeafDescendants(nodeId);
9118
- }
9119
- finally {
9120
- this.isUpdatingSelection = false;
9121
- }
9149
+ finally {
9150
+ this.isUpdatingSelection = false;
9122
9151
  }
9123
9152
  }
9124
9153
  else {
9125
- // Single selection mode: just update selectedNodeIds
9126
- if (isSelected) {
9127
- this.selectedNodeIds.set([nodeId]);
9154
+ // DESELECTION: Remove node (if leaf) and all leaf descendants from selectedNodeIds
9155
+ this.isUpdatingSelection = true;
9156
+ try {
9157
+ await this.deselectAllLeafDescendants(nodeId);
9128
9158
  }
9129
- else {
9130
- this.selectedNodeIds.set([]);
9159
+ finally {
9160
+ this.isUpdatingSelection = false;
9131
9161
  }
9132
9162
  }
9133
9163
  }
@@ -9143,7 +9173,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9143
9173
  * Clears all selected items
9144
9174
  */
9145
9175
  onClearAll() {
9146
- this.selectedNodeIds.set([]);
9176
+ this.setSelectedNodeIds([]);
9147
9177
  const treeComponent = this.tree();
9148
9178
  if (treeComponent) {
9149
9179
  treeComponent.deselectAll();
@@ -9194,7 +9224,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9194
9224
  for (const leafId of leafIds) {
9195
9225
  currentSelected.add(leafId);
9196
9226
  }
9197
- this.selectedNodeIds.set(Array.from(currentSelected));
9227
+ this.setSelectedNodeIds(Array.from(currentSelected));
9198
9228
  // Select all leaf nodes visually in the tree
9199
9229
  if (treeComponent) {
9200
9230
  for (const leafId of leafIds) {
@@ -9223,7 +9253,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9223
9253
  try {
9224
9254
  // Special case: deselecting 'all' clears everything
9225
9255
  if (parentId === 'all') {
9226
- this.selectedNodeIds.set([]);
9256
+ this.setSelectedNodeIds([]);
9227
9257
  // Use tree's deselectAll() to clear all visual selections
9228
9258
  const treeComponent = this.tree();
9229
9259
  if (treeComponent) {
@@ -9244,7 +9274,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9244
9274
  // Only update our internal state - tree handles visual state
9245
9275
  const currentSelected = this.selectedNodeIds();
9246
9276
  const newSelected = currentSelected.filter((id) => !leafIds.has(id));
9247
- this.selectedNodeIds.set(newSelected);
9277
+ this.setSelectedNodeIds(newSelected);
9248
9278
  }
9249
9279
  catch (error) {
9250
9280
  console.error(`Error deselecting leaf descendants for node ${parentId}:`, error);
@@ -9494,60 +9524,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9494
9524
  await new Promise((r) => setTimeout(r, pollMs));
9495
9525
  }
9496
9526
  }
9497
- /**
9498
- * Syncs selection state with the tree component.
9499
- * Selects leaf nodes and manually updates parent states (indeterminate/selected).
9500
- */
9501
- async syncSelectionWithTree(selectedIds) {
9502
- const treeComponent = this.tree();
9503
- if (!treeComponent || selectedIds.length === 0) {
9504
- return;
9505
- }
9506
- // Wait for tree to be fully initialized after ancestor expansion
9507
- await new Promise((resolve) => setTimeout(resolve, 200));
9508
- // Keep track of successfully synced nodes
9509
- const syncedNodes = new Set();
9510
- // Try multiple times to sync selection (nodes might load progressively)
9511
- for (let attempt = 0; attempt < 5; attempt++) {
9512
- for (const id of selectedIds) {
9513
- if (id && id !== 'all' && !syncedNodes.has(id)) {
9514
- try {
9515
- const node = treeComponent.findNode(id);
9516
- if (node) {
9517
- // Node exists in tree - mark it as selected using both methods
9518
- // 1. Direct property assignment for immediate visual feedback
9519
- node['selected'] = true;
9520
- node['indeterminate'] = false;
9521
- // 2. Use selectNode API for tree's internal state tracking
9522
- try {
9523
- treeComponent.selectNode(id);
9524
- }
9525
- catch {
9526
- // selectNode might fail but direct assignment should work
9527
- }
9528
- syncedNodes.add(id);
9529
- }
9530
- }
9531
- catch {
9532
- // Node not in tree yet
9533
- }
9534
- }
9535
- }
9536
- // If all nodes are synced, we're done
9537
- if (syncedNodes.size === selectedIds.length) {
9538
- break;
9539
- }
9540
- // Wait before next attempt
9541
- await new Promise((resolve) => setTimeout(resolve, 100));
9542
- }
9543
- // Now update parent states (indeterminate/selected) based on children
9544
- // This uses bottom-up traversal with datasource fallback for loading children
9545
- await this.updateParentStatesAfterSelection(selectedIds);
9546
- // Refresh tree to apply changes
9547
- treeComponent.refresh();
9548
- // Trigger change detection to update UI
9549
- this.changeDetectorRef.markForCheck();
9550
- }
9551
9527
  /**
9552
9528
  * Updates parent node states (selected/indeterminate) based on children selection.
9553
9529
  * Called after leaf nodes are selected to properly show parent states.
@@ -9741,35 +9717,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9741
9717
  const id = String(parentIdValue);
9742
9718
  return id && id !== 'all' ? id : null;
9743
9719
  }
9744
- /**
9745
- * Marks nodes as selected in the tree structure based on selectedNodeIds.
9746
- * This ensures pre-selected nodes appear selected when the tree is rendered.
9747
- */
9748
- markNodesAsSelected(node) {
9749
- const selectedIds = this.selectedNodeIds();
9750
- if (selectedIds.length === 0) {
9751
- return;
9752
- }
9753
- // Mark the node itself if it's selected
9754
- this.markNodeAsSelectedIfNeeded(node);
9755
- // Recursively mark children
9756
- const nodeChildren = node['children'];
9757
- if (nodeChildren) {
9758
- nodeChildren.forEach((child) => {
9759
- this.markNodesAsSelected(child);
9760
- });
9761
- }
9762
- }
9763
- /**
9764
- * Marks a single node as selected if it's in the selectedNodeIds list.
9765
- */
9766
- markNodeAsSelectedIfNeeded(node) {
9767
- const selectedIds = this.selectedNodeIds();
9768
- const nodeId = String(node['id'] ?? '');
9769
- if (nodeId && selectedIds.includes(nodeId)) {
9770
- node['selected'] = true;
9771
- }
9772
- }
9773
9720
  /**
9774
9721
  * Marks a node and its children as disabled if the node ID matches the excluded ID.
9775
9722
  */
@@ -9778,7 +9725,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9778
9725
  if (nodeId === excludedId) {
9779
9726
  node['disabled'] = true;
9780
9727
  }
9781
- // Recursively mark children
9782
9728
  const nodeChildren = node['children'];
9783
9729
  if (nodeChildren) {
9784
9730
  nodeChildren.forEach((child) => {
@@ -9787,41 +9733,16 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9787
9733
  }
9788
9734
  }
9789
9735
  /**
9790
- * Processes root node: marks excluded as disabled, marks selected, and syncs selection with tree component
9736
+ * Processes root node: marks excluded nodes as disabled.
9791
9737
  */
9792
9738
  processRootNode(rootNode) {
9793
9739
  const excludedId = this.excludedNodeId();
9794
9740
  if (excludedId) {
9795
9741
  this.markNodeAsDisabled(rootNode, excludedId);
9796
9742
  }
9797
- this.markNodesAsSelected(rootNode);
9798
- // Sync selection with tree component after a short delay to ensure tree is rendered
9799
- const treeComponent = this.tree();
9800
- if (treeComponent) {
9801
- setTimeout(() => {
9802
- this.isUpdatingSelection = true;
9803
- try {
9804
- const selectedIds = this.selectedNodeIds();
9805
- selectedIds.forEach((id) => {
9806
- if (id && id !== 'all') {
9807
- try {
9808
- treeComponent.selectNode(id);
9809
- }
9810
- catch {
9811
- // Node might not be in tree yet
9812
- }
9813
- }
9814
- });
9815
- }
9816
- finally {
9817
- this.isUpdatingSelection = false;
9818
- }
9819
- }, 0);
9820
- }
9821
9743
  }
9822
9744
  /**
9823
9745
  * Processes child nodes: marks excluded as disabled
9824
- * Selection marking is handled in datasource callback ONLY during initial load
9825
9746
  */
9826
9747
  processChildNodes(childNodes) {
9827
9748
  const excludedId = this.excludedNodeId();
@@ -9967,8 +9888,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9967
9888
  return this.translationService.resolve(raw);
9968
9889
  }
9969
9890
  async getSelectedItems() {
9970
- // selectedNodeIds now only contains LEAF nodes (already filtered)
9971
- const selectedIds = this.selectedNodeIds();
9891
+ const selectedIds = this.normalizeSelectedIds(this.selectedNodeIds());
9972
9892
  if (selectedIds.length === 0) {
9973
9893
  return [];
9974
9894
  }
@@ -10061,6 +9981,9 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
10061
9981
  [dragBehavior]="'none'"
10062
9982
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
10063
9983
  [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
9984
+ [controlledSelection]="!allowMultiple()"
9985
+ [selectedIds]="selectedNodeIds()"
9986
+ (selectedIdsChange)="onControlledSelectedIdsChange($event)"
10064
9987
  [showIcons]="true"
10065
9988
  [titleField]="textField()"
10066
9989
  [idField]="valueField()"
@@ -10147,7 +10070,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
10147
10070
  ></ax-button>
10148
10071
  </ax-suffix>
10149
10072
  </ax-footer>
10150
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay"], outputs: ["datasourceChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10073
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay", "controlledSelection", "selectedIds"], outputs: ["datasourceChange", "selectedIdsChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10151
10074
  }
10152
10075
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityCategoryTreeSelectorComponent, decorators: [{
10153
10076
  type: Component,
@@ -10198,6 +10121,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
10198
10121
  [dragBehavior]="'none'"
10199
10122
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
10200
10123
  [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
10124
+ [controlledSelection]="!allowMultiple()"
10125
+ [selectedIds]="selectedNodeIds()"
10126
+ (selectedIdsChange)="onControlledSelectedIdsChange($event)"
10201
10127
  [showIcons]="true"
10202
10128
  [titleField]="textField()"
10203
10129
  [idField]="valueField()"
@@ -11307,9 +11233,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11307
11233
  this.isOpen.set(true);
11308
11234
  const currentValue = this.getValue();
11309
11235
  const selectedIds = currentValue
11310
- ? (this.multiple() ? castArray(currentValue) : [currentValue])
11311
- .map((v) => String(extractValue(v, this.valueField())))
11312
- .filter((id) => id && id !== 'all')
11236
+ ? this.normalizePopupSelectedIds(currentValue)
11313
11237
  : [];
11314
11238
  // Get current entity ID from context (if editing an entity)
11315
11239
  const currentEntityId = this.contextService.getValue('id');
@@ -11330,9 +11254,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11330
11254
  },
11331
11255
  });
11332
11256
  if (result?.data?.selected && Array.isArray(result.data.selected)) {
11333
- // Use the popup result directly as the source of truth
11334
- // This ensures that deselected items are properly removed
11335
- const newItems = result.data.selected;
11257
+ const newItems = this.multiple() ? result.data.selected : result.data.selected.slice(0, 1);
11336
11258
  if (newItems.length === 0) {
11337
11259
  // All items were deselected
11338
11260
  this.clear();
@@ -11482,10 +11404,10 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11482
11404
  }
11483
11405
  });
11484
11406
  this.contextService.applyObjectPaths(itemToExpose, { origin: 'user' });
11485
- this.setUserValue(this.singleOrMultiple(keys));
11407
+ this.setValue(this.singleOrMultiple(keys));
11486
11408
  }
11487
11409
  else {
11488
- this.setUserValue(this.singleOrMultiple(keys));
11410
+ this.setValue(this.singleOrMultiple(keys));
11489
11411
  }
11490
11412
  // Trigger change detection
11491
11413
  this.cdr.markForCheck();
@@ -11500,6 +11422,12 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11500
11422
  singleOrMultiple(values) {
11501
11423
  return this.multiple() ? values : values[0];
11502
11424
  }
11425
+ normalizePopupSelectedIds(currentValue) {
11426
+ const ids = (this.multiple() ? castArray(currentValue) : castArray(currentValue).slice(0, 1))
11427
+ .map((v) => String(extractValue(v, this.valueField())))
11428
+ .filter((id) => id && id !== 'all');
11429
+ return this.multiple() ? ids : ids.slice(0, 1);
11430
+ }
11503
11431
  getItemLabel(item) {
11504
11432
  if (!item) {
11505
11433
  return '';
@@ -12777,11 +12705,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12777
12705
  }
12778
12706
  untracked(() => void this.applyRelatedFiltersFromContextAndDatasource(specs));
12779
12707
  }, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
12780
- /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
12708
+ /** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
12781
12709
  this.#patchDataListRefreshEffect = effect(() => {
12782
12710
  const inst = this.listWidget()?.instance;
12783
- const opts = this.options();
12784
- if (!inst?.refresh || inst.__axpEntityListRefreshPatched || !opts['syncRelatedListFiltersFromDialogContext']) {
12711
+ if (!inst?.refresh || inst.__axpEntityListRefreshPatched) {
12785
12712
  return;
12786
12713
  }
12787
12714
  inst.__axpEntityListRefreshPatched = true;
@@ -12793,6 +12720,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12793
12720
  if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
12794
12721
  await this.applyRelatedFiltersFromContextAndDatasource(specs);
12795
12722
  }
12723
+ else {
12724
+ await this.reapplyScopedFiltersToDataSource();
12725
+ }
12796
12726
  originalRefresh();
12797
12727
  })();
12798
12728
  };
@@ -13013,6 +12943,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13013
12943
  * Applies merged route + parent-scope filters to the widget value and data source.
13014
12944
  */
13015
12945
  applyMergedRouteFiltersToList() {
12946
+ void this.reapplyScopedFiltersToDataSource();
12947
+ }
12948
+ /** Syncs toolbar filters to context and AXDataSource (retries until data-list is mounted). */
12949
+ async reapplyScopedFiltersToDataSource() {
13016
12950
  const merged = this.getMergedToolbarFilters();
13017
12951
  const current = this.getValue();
13018
12952
  if (!isEqual$1(current?.toolbar?.filters, merged)) {
@@ -13021,7 +12955,26 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13021
12955
  toolbar: { ...(current?.toolbar ?? {}), filters: merged },
13022
12956
  });
13023
12957
  }
12958
+ const pushed = await this.ensureToolbarFiltersOnDataSource();
12959
+ if (pushed) {
12960
+ this.triggerInitialLoadIfNeeded();
12961
+ }
12962
+ }
12963
+ /**
12964
+ * Entity lists use `fetchDataMode: 'manual'`; after scoped filters reach the data source,
12965
+ * the grid must be refreshed once (initial #effect may run before the data-list is ready).
12966
+ */
12967
+ triggerInitialLoadIfNeeded() {
12968
+ if (this.isMounted()) {
12969
+ return;
12970
+ }
12971
+ const listInstance = this.listWidget()?.instance;
12972
+ if (!listInstance?.call) {
12973
+ return;
12974
+ }
13024
12975
  this.pushToolbarFiltersToDataSource();
12976
+ listInstance.call('refresh');
12977
+ this.isMounted.set(true);
13025
12978
  }
13026
12979
  /**
13027
12980
  * Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
@@ -13068,26 +13021,29 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13068
13021
  return true;
13069
13022
  }
13070
13023
  /**
13071
- * Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
13024
+ * Pushes toolbar filters once the embedded data-list exposes its data source
13025
+ * (listNode is created via deferred setTimeout in ngOnInit).
13072
13026
  */
13073
- async applyRelatedFiltersFromContextAndDatasource(specs) {
13074
- await this.applyRelatedFiltersFromContext(specs);
13027
+ async ensureToolbarFiltersOnDataSource() {
13075
13028
  if (this.pushToolbarFiltersToDataSource()) {
13076
- return;
13077
- }
13078
- const opts = this.options();
13079
- if (!opts['syncRelatedListFiltersFromDialogContext']) {
13080
- return;
13029
+ return true;
13081
13030
  }
13082
- /** Data-list is created in ngOnInit via deferred listNode.set; retry briefly until instance exposes dataSource. */
13083
13031
  const maxAttempts = 40;
13084
13032
  const delayMs = 50;
13085
13033
  for (let i = 0; i < maxAttempts; i++) {
13086
13034
  await new Promise((resolve) => setTimeout(resolve, delayMs));
13087
13035
  if (this.pushToolbarFiltersToDataSource()) {
13088
- return;
13036
+ return true;
13089
13037
  }
13090
13038
  }
13039
+ return false;
13040
+ }
13041
+ /**
13042
+ * Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
13043
+ */
13044
+ async applyRelatedFiltersFromContextAndDatasource(specs) {
13045
+ await this.applyRelatedFiltersFromContext(specs);
13046
+ await this.ensureToolbarFiltersOnDataSource();
13091
13047
  }
13092
13048
  /**
13093
13049
  * Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
@@ -13095,7 +13051,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13095
13051
  refreshGridWithParentScopedFilters() {
13096
13052
  this.listWidget()?.instance?.call('refresh');
13097
13053
  }
13098
- /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
13054
+ /** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
13099
13055
  #patchDataListRefreshEffect;
13100
13056
  #effect;
13101
13057
  /**
@@ -13110,7 +13066,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13110
13066
  handleQueryChanges(queries, dataSource, listInstance, isMounted) {
13111
13067
  const changeTracker = this.analyzeChanges(queries);
13112
13068
  this.previousQueries = queries;
13113
- this.applyDataSourceChanges(dataSource, queries, changeTracker);
13069
+ this.applyDataSourceChanges(dataSource, queries, changeTracker, isMounted);
13114
13070
  this.handleListRefresh(listInstance, changeTracker, isMounted);
13115
13071
  this.handleColumnChanges(changeTracker);
13116
13072
  }
@@ -13137,8 +13093,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13137
13093
  /**
13138
13094
  * Applies filter and sort changes to the data source
13139
13095
  */
13140
- applyDataSourceChanges(dataSource, queries, changeTracker) {
13141
- if (changeTracker.isFilterChanged) {
13096
+ applyDataSourceChanges(dataSource, queries, changeTracker, isMounted) {
13097
+ const shouldApplyFilters = changeTracker.isFilterChanged || (!isMounted && (queries?.filters?.length ?? 0) > 0);
13098
+ if (shouldApplyFilters) {
13142
13099
  dataSource.filter({
13143
13100
  filters: queries?.filters,
13144
13101
  });
@@ -13149,16 +13106,16 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13149
13106
  }
13150
13107
  }
13151
13108
  /**
13152
- * Handles list refresh logic based on changes and mount status
13109
+ * Handles list refresh logic based on changes and mount status.
13110
+ * Initial fetch is owned by {@link triggerInitialLoadIfNeeded} (manual fetchDataMode).
13153
13111
  */
13154
13112
  handleListRefresh(listInstance, changeTracker, isMounted) {
13155
- const shouldRefresh = changeTracker.isFilterChanged || changeTracker.isSortChanged || !isMounted;
13156
- if (shouldRefresh) {
13113
+ if (!isMounted) {
13114
+ return;
13115
+ }
13116
+ if (changeTracker.isFilterChanged || changeTracker.isSortChanged) {
13117
+ this.pushToolbarFiltersToDataSource();
13157
13118
  listInstance.call('refresh');
13158
- // Set mounted flag only on initial load (not when filters/sorts change)
13159
- if (!changeTracker.isFilterChanged && !changeTracker.isSortChanged) {
13160
- this.isMounted.set(true);
13161
- }
13162
13119
  }
13163
13120
  }
13164
13121
  /**
@@ -15597,8 +15554,8 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15597
15554
  this.setLoading = (loading) => {
15598
15555
  this.isLoading.set(loading);
15599
15556
  };
15600
- this.setItems = (items, options) => {
15601
- const origin = options?.origin ?? 'system';
15557
+ /** @param syncContext When false, only updates UI (hydration/revert); skips context writes. */
15558
+ this.setItems = (items, syncContext = true) => {
15602
15559
  // Ensure items is always an array
15603
15560
  items = castArray(items);
15604
15561
  // Filter out null/undefined items
@@ -15614,13 +15571,13 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15614
15571
  return item;
15615
15572
  });
15616
15573
  this.selectedItems.set(items);
15617
- //
15574
+ if (!syncContext) {
15575
+ return;
15576
+ }
15618
15577
  const keys = items.map((item) => get(item, this.valueField()));
15619
15578
  const text = items.map((item) => this.mlsResolver.resolve(get(item, displayField)));
15620
- //
15621
- // extract data from valueField and set context by expose path
15622
15579
  if (this.expose()) {
15623
- this.expoesItems(origin);
15580
+ this.expoesItems();
15624
15581
  }
15625
15582
  const newValue = this.singleOrMultiple(keys);
15626
15583
  if (this.filterMode()) {
@@ -15630,10 +15587,10 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15630
15587
  operation: {
15631
15588
  type: this.multiple() ? 'in' : 'equal',
15632
15589
  },
15633
- }, { origin });
15590
+ });
15634
15591
  }
15635
15592
  else {
15636
- this.setValue(newValue, { origin });
15593
+ this.setValue(newValue);
15637
15594
  }
15638
15595
  };
15639
15596
  }
@@ -15647,7 +15604,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15647
15604
  ? castArray(this.filterMode() ? rawValue.value : rawValue)
15648
15605
  : [rawValue].filter((v) => v != null);
15649
15606
  if (!values.length) {
15650
- this.setItems([]);
15607
+ this.setItems([], false);
15651
15608
  this.isLoading.set(false);
15652
15609
  return;
15653
15610
  }
@@ -15662,22 +15619,22 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15662
15619
  .map((id) => entityDataAccessor.byKey(id)));
15663
15620
  // Filter out null/undefined results
15664
15621
  const validItems = items.filter((item) => item != null);
15665
- this.setItems(validItems);
15622
+ this.setItems(validItems, false);
15666
15623
  }
15667
15624
  else {
15668
15625
  const id = extractValue(this.filterMode() ? values[0].value : values[0], this.valueField());
15669
15626
  if (id != null) {
15670
15627
  const item = await entityDataAccessor.byKey(id);
15671
- this.setItems(item != null ? [item] : []);
15628
+ this.setItems(item != null ? [item] : [], false);
15672
15629
  }
15673
15630
  else {
15674
- this.setItems([]);
15631
+ this.setItems([], false);
15675
15632
  }
15676
15633
  }
15677
15634
  }
15678
15635
  catch (error) {
15679
15636
  console.error('[LookupWidget] findByValue() error:', error);
15680
- this.setItems([]);
15637
+ this.setItems([], false);
15681
15638
  }
15682
15639
  finally {
15683
15640
  this.isLoading.set(false);
@@ -15685,7 +15642,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15685
15642
  }
15686
15643
  //#region ---- Event Handlers from Dumb Components ----
15687
15644
  handleComponentValueChanged(items) {
15688
- this.setItems(items ?? [], { origin: 'user' });
15645
+ this.setItems(items ?? []);
15689
15646
  }
15690
15647
  async handleCreateClick(_e) {
15691
15648
  const entity = this.entityDef();
@@ -15710,10 +15667,10 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15710
15667
  if (this.multiple()) {
15711
15668
  const exists = currentItems.some((item) => get(item, valueField) === newId);
15712
15669
  const newItems = exists ? currentItems : [...currentItems, createdItem];
15713
- this.setItems(newItems, { origin: 'user' });
15670
+ this.setItems(newItems);
15714
15671
  }
15715
15672
  else {
15716
- this.setItems([createdItem], { origin: 'user' });
15673
+ this.setItems([createdItem]);
15717
15674
  }
15718
15675
  }
15719
15676
  }
@@ -15730,7 +15687,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15730
15687
  * so e.g. `{ person: { educationLevel: { id, title } } }` would replace the entire `person`
15731
15688
  * object and drop sibling fields like `person.educationLevelId`, causing a value/effect loop.
15732
15689
  */
15733
- expoesItems(origin = 'system') {
15690
+ expoesItems() {
15734
15691
  const exposeValue = castArray(this.expose());
15735
15692
  const items = this.selectedItems();
15736
15693
  const isEmpty = !items || items.length === 0;
@@ -15743,7 +15700,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15743
15700
  parentPaths.add(pathParts.slice(0, -1).join('.'));
15744
15701
  }
15745
15702
  else {
15746
- this.contextService.update(i, null, { origin });
15703
+ this.contextService.update(i, null);
15747
15704
  }
15748
15705
  }
15749
15706
  else {
@@ -15752,25 +15709,25 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15752
15709
  parentPaths.add(pathParts.slice(0, -1).join('.'));
15753
15710
  }
15754
15711
  else {
15755
- this.contextService.update(i.target, null, { origin });
15712
+ this.contextService.update(i.target, null);
15756
15713
  }
15757
15714
  }
15758
15715
  });
15759
15716
  parentPaths.forEach((parentPath) => {
15760
- this.contextService.update(parentPath, null, { origin });
15717
+ this.contextService.update(parentPath, null);
15761
15718
  });
15762
15719
  return;
15763
15720
  }
15764
15721
  exposeValue.forEach((i) => {
15765
15722
  if (typeof i === 'string') {
15766
15723
  const values = items.map((item) => set({}, i, get(item, i)));
15767
- this.contextService.update(i, this.singleOrMultiple(values), { origin });
15724
+ this.contextService.update(i, this.singleOrMultiple(values));
15768
15725
  }
15769
15726
  else {
15770
15727
  const values = this.multiple()
15771
15728
  ? items.map((item) => set({}, i.source, get(item, i.source)))
15772
15729
  : items.map((item) => get(item, i.source));
15773
- this.contextService.update(i.target, this.singleOrMultiple(values), { origin });
15730
+ this.contextService.update(i.target, this.singleOrMultiple(values));
15774
15731
  }
15775
15732
  });
15776
15733
  }
@@ -16185,7 +16142,7 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
16185
16142
  if (!resolvedTitle) {
16186
16143
  const displayTitle = formatLookupItemDisplay(headerItem, this.entityDef(), this.lookupDisplayOptions(), this.formatService, (value) => this.translation.resolve(value));
16187
16144
  resolvedTitle = typeof displayTitle === 'string' ? displayTitle : this.translation.resolve(displayTitle);
16188
- if (isUnresolvedLookupDisplayTemplate(resolvedTitle)) {
16145
+ if (isUnresolvedEntityDisplayTemplate(resolvedTitle)) {
16189
16146
  resolvedTitle = '';
16190
16147
  }
16191
16148
  }
@@ -19105,32 +19062,10 @@ function hasFileUploaderTitleOrDescriptionFields(editDialog) {
19105
19062
  }
19106
19063
  //#endregion
19107
19064
 
19108
- //#region ---- Imports ----
19065
+ //#region ---- Imports ----
19109
19066
  //#endregion
19110
- //#region ---- Title template resolution ----
19067
+ //#region ---- Title template resolution ----
19111
19068
  const INVALID_DISPLAY_TEXT = '[object Object]';
19112
- /**
19113
- * Row title template for entity-scoped popovers (e.g. attachment file list).
19114
- * Priority: `interfaces.master.single.title` → `formats.searchResult.title` → `formats.individual`.
19115
- */
19116
- function resolveEntityRowTitleTemplate(entity) {
19117
- if (!entity) {
19118
- return undefined;
19119
- }
19120
- const singleTitle = entity.interfaces?.master?.single?.title?.trim();
19121
- if (singleTitle) {
19122
- return normalizeLookupDisplayTemplate(singleTitle);
19123
- }
19124
- const searchTitle = entity.formats?.searchResult?.title?.trim();
19125
- if (searchTitle) {
19126
- return normalizeLookupDisplayTemplate(searchTitle);
19127
- }
19128
- const individual = entity.formats?.individual?.trim();
19129
- if (individual) {
19130
- return normalizeLookupDisplayTemplate(individual);
19131
- }
19132
- return undefined;
19133
- }
19134
19069
  /** Resolves a row field or format result to a plain display string for the active locale. */
19135
19070
  function resolveEntityRowDisplayText(value, activeLang) {
19136
19071
  if (value == null || value === '') {
@@ -19138,7 +19073,7 @@ function resolveEntityRowDisplayText(value, activeLang) {
19138
19073
  }
19139
19074
  if (typeof value === 'string') {
19140
19075
  const text = value.trim();
19141
- if (!text || text === INVALID_DISPLAY_TEXT || isUnresolvedLookupDisplayTemplate(text)) {
19076
+ if (!text || text === INVALID_DISPLAY_TEXT || isUnresolvedEntityDisplayTemplate(text)) {
19142
19077
  return '';
19143
19078
  }
19144
19079
  return text;
@@ -19156,21 +19091,25 @@ function resolveEntityRowDisplayText(value, activeLang) {
19156
19091
  return '';
19157
19092
  }
19158
19093
  /**
19159
- * Formats the entity row title for display using row fields and single-view template.
19094
+ * Formats the entity row title for display using entity-driven templates and property paths.
19160
19095
  */
19161
19096
  function formatEntityRowDisplayTitle(entity, rowData, formatService, activeLang) {
19162
19097
  if (!rowData) {
19163
19098
  return '';
19164
19099
  }
19165
- for (const field of ['title', 'name', 'code']) {
19166
- const fromField = resolveEntityRowDisplayText(get(rowData, field), activeLang);
19100
+ const titleContext = buildEntitySearchTitleContext(entity ?? {});
19101
+ for (const path of titleContext.fallbackFields) {
19102
+ const fromField = resolveEntityRowDisplayText(get(rowData, path), activeLang);
19167
19103
  if (fromField) {
19168
19104
  return fromField;
19169
19105
  }
19170
19106
  }
19171
- const template = resolveEntityRowTitleTemplate(entity);
19172
- if (template) {
19173
- const formatted = formatService.format(template, 'string', rowData);
19107
+ const templates = [
19108
+ resolveEntityRowTitleTemplate(entity),
19109
+ ...titleContext.fallbackTemplates,
19110
+ ].filter((template) => !!template?.trim());
19111
+ for (const template of templates) {
19112
+ const formatted = formatService.format(normalizeEntityDisplayTemplate(template), 'string', rowData);
19174
19113
  const fromTemplate = resolveEntityRowDisplayText(formatted, activeLang);
19175
19114
  if (fromTemplate) {
19176
19115
  return fromTemplate;
@@ -21522,7 +21461,7 @@ class AXPLayoutAdapterBuilder {
21522
21461
  load: this.createLoadFunction(),
21523
21462
  pages: this.adapter.pages || [],
21524
21463
  exitUrl: this.adapter.exitUrl,
21525
- //getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
21464
+ getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
21526
21465
  getPageStatus: (context, currentPage) => this.badgeStatusService.getPageStatus(name, context, currentPage),
21527
21466
  };
21528
21467
  }
@@ -22210,6 +22149,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
22210
22149
  title: context.rootTitle ?? '',
22211
22150
  label: relatedEntity.title,
22212
22151
  icon: relatedEntity.icon || entityDef.icon,
22152
+ /** List-only page: scoped filters are system state, not user edits. */
22153
+ isReadonly: true,
22213
22154
  actions: this.mergeActions(entityDef, actions)
22214
22155
  ?.filter((a) => a.priority === 'primary' && !a.isChild)
22215
22156
  ?.map((a) => {
@@ -22892,6 +22833,7 @@ class AXPMainEntityContentBuilder {
22892
22833
  icon: 'fa-light fa-rotate-left',
22893
22834
  color: 'default',
22894
22835
  visible: '{{context.isDirty()}}',
22836
+ shortcuts: ['escape'],
22895
22837
  command: {
22896
22838
  name: 'discard',
22897
22839
  },
@@ -22901,6 +22843,7 @@ class AXPMainEntityContentBuilder {
22901
22843
  icon: 'fa-light fa-floppy-disk',
22902
22844
  color: 'primary',
22903
22845
  visible: '{{context.isDirty()}}',
22846
+ shortcuts: ['ctrl+s'],
22904
22847
  command: {
22905
22848
  name: 'update-entity',
22906
22849
  },
@@ -23194,6 +23137,7 @@ class AXPMainEntityContentBuilder {
23194
23137
  icon: action.icon,
23195
23138
  color: action.color,
23196
23139
  disabled: disabled || false,
23140
+ shortcuts: action.shortcuts,
23197
23141
  zone: 'header',
23198
23142
  priority: action.priority,
23199
23143
  scope: action.scope === AXPEntityCommandScope.Individual ? AXPEntityCommandScope.TypeLevel : action.scope,
@@ -23256,17 +23200,25 @@ class AXPLayoutAdapterFactory {
23256
23200
  async createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies) {
23257
23201
  const entity = await entityResolver.resolve(moduleName, entityName);
23258
23202
  if (!entity) {
23259
- throw new Error(`Entity ${moduleName}.${entityName} not found`);
23203
+ throw new AXPNotFoundError(`Entity ${moduleName}.${entityName} not found`);
23260
23204
  }
23261
23205
  const rootContext = await this.loadRootContext(entity, id);
23262
- // Evaluate hidden expressions for related entities once, reuse across building and composing
23263
- const evaluatedRelatedEntities = await this.evaluateRelatedEntitiesHidden(entity?.relatedEntities ?? [], rootContext, dependencies);
23264
- // Build main and related pages using evaluated related entities
23265
- const entityWithEvaluatedRelated = { ...entity, relatedEntities: evaluatedRelatedEntities };
23266
- const mainPage = await this.buildMainPage(entityWithEvaluatedRelated, rootContext, id, dependencies);
23267
- const relatedPages = await this.buildRelatedPages(entityWithEvaluatedRelated, rootContext, dependencies);
23206
+ if (axpIsEntityRecordNotFound(rootContext)) {
23207
+ throw new AXPNotFoundError(`Entity record ${moduleName}.${entityName}#${id} not found`);
23208
+ }
23209
+ // Evaluate hidden expressions for related entities and component pages once, reuse across building and composing
23210
+ const evaluatedRelatedEntities = await this.evaluateHiddenFlags(entity?.relatedEntities ?? [], rootContext, dependencies);
23211
+ const evaluatedPages = await this.evaluateHiddenFlags(entity?.pages ?? [], rootContext, dependencies);
23212
+ // Build main and related pages using evaluated related entities and pages
23213
+ const entityWithEvaluatedLayout = {
23214
+ ...entity,
23215
+ relatedEntities: evaluatedRelatedEntities,
23216
+ pages: evaluatedPages,
23217
+ };
23218
+ const mainPage = await this.buildMainPage(entityWithEvaluatedLayout, rootContext, id, dependencies);
23219
+ const relatedPages = await this.buildRelatedPages(entityWithEvaluatedLayout, rootContext, dependencies);
23268
23220
  // Compose ordered pages around the primary page
23269
- const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedRelated);
23221
+ const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedLayout);
23270
23222
  const applicationName = dependencies.session.application?.name ?? 'default';
23271
23223
  const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
23272
23224
  return this.layoutAdapterBuilder
@@ -23285,15 +23237,15 @@ class AXPLayoutAdapterFactory {
23285
23237
  return this.mainEntityContentBuilder.build(entity, rootContext, { ...dependencies, reloadRootContext: () => this.loadRootContext(entity, id) }, await this.getRootTitle(entity, rootContext, dependencies));
23286
23238
  }
23287
23239
  /**
23288
- * Evaluates the 'hidden' expression for each related entity using the expression evaluator.
23289
- * Returns a new array of related entities with the evaluated hidden values.
23240
+ * Evaluates the 'hidden' expression for layout items using the expression evaluator.
23241
+ * Returns a new array with evaluated hidden values.
23290
23242
  */
23291
- async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
23292
- if (!relatedEntities?.length || !dependencies?.expressionEvaluator) {
23293
- return relatedEntities ?? [];
23243
+ async evaluateHiddenFlags(items, rootContext, dependencies) {
23244
+ if (!items?.length || !dependencies?.expressionEvaluator) {
23245
+ return items ?? [];
23294
23246
  }
23295
- return Promise.all(relatedEntities.map(async (re) => {
23296
- let hidden = re.hidden;
23247
+ return Promise.all(items.map(async (item) => {
23248
+ let hidden = item.hidden;
23297
23249
  if (hidden && typeof hidden === 'string') {
23298
23250
  try {
23299
23251
  const scope = {
@@ -23308,9 +23260,12 @@ class AXPLayoutAdapterFactory {
23308
23260
  // Keep original hidden value if evaluation fails
23309
23261
  }
23310
23262
  }
23311
- return { ...re, hidden };
23263
+ return { ...item, hidden };
23312
23264
  }));
23313
23265
  }
23266
+ async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
23267
+ return this.evaluateHiddenFlags(relatedEntities, rootContext, dependencies);
23268
+ }
23314
23269
  async buildRelatedPages(entity, rootContext, dependencies) {
23315
23270
  const pages = [];
23316
23271
  const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
@@ -23475,7 +23430,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
23475
23430
  }]
23476
23431
  }], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4$3.AXPFilterOperatorMiddlewareService }] });
23477
23432
 
23478
- const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory)) => {
23433
+ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory), router = inject(Router)) => {
23479
23434
  const moduleName = route.parent?.paramMap.get('module');
23480
23435
  const entityName = route.paramMap.get('entity');
23481
23436
  const id = route.paramMap.get('id');
@@ -23487,8 +23442,15 @@ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver =
23487
23442
  workflowService,
23488
23443
  commandService,
23489
23444
  };
23490
- const adapter = await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
23491
- return adapter;
23445
+ try {
23446
+ return await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
23447
+ }
23448
+ catch (error) {
23449
+ if (error instanceof AXPNotFoundError) {
23450
+ return axpRedirectToNotFound(router);
23451
+ }
23452
+ throw error;
23453
+ }
23492
23454
  };
23493
23455
 
23494
23456
  class AXPEntityPreloadFilterGuard {
@@ -24262,7 +24224,7 @@ const AXPCrudModifier = {
24262
24224
  queries.list = {
24263
24225
  execute: async (e) => {
24264
24226
  const res = await dataService.query(e);
24265
- console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
24227
+ // console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
24266
24228
  return res;
24267
24229
  },
24268
24230
  type: AXPEntityQueryType.List,
@@ -24305,6 +24267,8 @@ class AXPEntitySearchDefinitionProvider {
24305
24267
  this.entityRegister.getAll().forEach((entity) => {
24306
24268
  const manifest = this.manifestRegistry.get(entity.module);
24307
24269
  const i18nScope = manifest?.i18n ?? moduleNameToI18nScope(entity.module);
24270
+ const searchTitleTemplate = entity.formats.searchResult?.title;
24271
+ const titleContext = buildEntitySearchTitleContext(entity, searchTitleTemplate);
24308
24272
  context.addDefinition(`Module.${entity.module}.${entity.name}`, `@${i18nScope}:module.title`, `Module.${entity.module}`, entity.icon ?? 'fa-solid fa-objects-column', 4, {
24309
24273
  actions: [
24310
24274
  {
@@ -24314,9 +24278,11 @@ class AXPEntitySearchDefinitionProvider {
24314
24278
  },
24315
24279
  ],
24316
24280
  format: {
24317
- title: entity.formats.searchResult?.title,
24281
+ title: searchTitleTemplate,
24318
24282
  description: (entity.formats.searchResult?.description) ?? `${entity.module} / ${entity.title}`,
24319
24283
  id: '{{data.id}}',
24284
+ titleFallbackTemplates: titleContext.fallbackTemplates,
24285
+ titleFallbackFields: titleContext.fallbackFields,
24320
24286
  },
24321
24287
  });
24322
24288
  });
@@ -25000,7 +24966,7 @@ function routesFacory() {
25000
24966
  loadComponent: () => {
25001
24967
  return config.viewers.root();
25002
24968
  },
25003
- canActivate: [AXPAuthGuard],
24969
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
25004
24970
  children: [
25005
24971
  {
25006
24972
  path: ':module',
@@ -25038,6 +25004,7 @@ function routesFacory() {
25038
25004
  loadComponent: () => {
25039
25005
  return config.viewers.master.details();
25040
25006
  },
25007
+ canDeactivate: [axpDetailsViewCanDeactivateGuard],
25041
25008
  runGuardsAndResolvers: (from, to) => {
25042
25009
  const entityChanged = from.params['module'] !== to.params['module'] || from.params['entity'] !== to.params['entity'];
25043
25010
  const idChanged = from.params['id'] !== to.params['id'];
@@ -25056,8 +25023,16 @@ function routesFacory() {
25056
25023
  redirectTo: ':entity/list',
25057
25024
  pathMatch: 'full',
25058
25025
  },
25026
+ {
25027
+ path: '**',
25028
+ redirectTo: AXP_NOT_FOUND_ROUTE,
25029
+ },
25059
25030
  ],
25060
25031
  },
25032
+ {
25033
+ path: '**',
25034
+ redirectTo: AXP_NOT_FOUND_ROUTE,
25035
+ },
25061
25036
  ],
25062
25037
  },
25063
25038
  ];
@@ -25376,38 +25351,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
25376
25351
  }]
25377
25352
  }] });
25378
25353
 
25379
- /** Entity query filter field for {@link AXPRecordWorkflowInfo.instanceId}. */
25380
- const AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD = 'workflowInfo.instanceId';
25381
- /** Entity query filter field for {@link AXPRecordWorkflowInfo.correlationId}. */
25382
- const AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD = 'workflowInfo.correlationId';
25383
- /** Entity query filter field for {@link AXPRecordWorkflowInfo.definitionId}. */
25384
- const AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD = 'workflowInfo.definitionId';
25385
- /** Returns a trimmed workflow instance id from a record, if present. */
25386
- function getRecordWorkflowInstanceId(record) {
25387
- const id = record?.workflowInfo?.instanceId?.trim();
25388
- return id && id.length > 0 ? id : null;
25389
- }
25390
- /** Returns a trimmed workflow correlation id from a record, if present. */
25391
- function getRecordWorkflowCorrelationId(record) {
25392
- const id = record?.workflowInfo?.correlationId?.trim();
25393
- return id && id.length > 0 ? id : null;
25394
- }
25395
- /** Builds canonical workflow metadata for a domain record (engine / middleware). */
25396
- function buildAXPRecordWorkflowInfo(params) {
25397
- const instanceId = params.instanceId?.trim();
25398
- if (!instanceId) {
25399
- return {};
25400
- }
25401
- const definitionId = params.definitionId?.trim();
25402
- const correlationId = params.correlationId?.trim();
25403
- return {
25404
- instanceId,
25405
- ...(definitionId ? { definitionId } : { definitionId: null }),
25406
- ...(correlationId ? { correlationId } : { correlationId: null }),
25407
- ...(params.pinnedVersion != null ? { pinnedVersion: params.pinnedVersion } : {}),
25408
- };
25409
- }
25410
-
25411
25354
  /**
25412
25355
  * Registers entity CRUD commands and queries for the given entity keys.
25413
25356
  * Use in app/module providers; consumes AXPEntityService from this package.
@@ -25539,7 +25482,7 @@ class AXPGetEntityDetailsQuery {
25539
25482
  constructor() {
25540
25483
  //#region ---- Services & Dependencies ----
25541
25484
  this.entityDefinitionRegistry = inject(AXPEntityDefinitionRegistryService);
25542
- this.injector = inject(Injector);
25485
+ this.entityService = inject(AXPEntityService);
25543
25486
  }
25544
25487
  //#region ---- Query Execution ----
25545
25488
  /**
@@ -25585,10 +25528,10 @@ class AXPGetEntityDetailsQuery {
25585
25528
  * @returns Promise resolving to entity data
25586
25529
  */
25587
25530
  async getEntityData(entity, id) {
25588
- const dataService = runInInjectionContext(this.injector, () => new AXMEntityCrudServiceImpl(entity));
25531
+ const dataService = this.entityService.withEntity(entity).data();
25589
25532
  try {
25590
25533
  // Fetch the entity data
25591
- return await dataService.getOne(id);
25534
+ return await dataService.byKey(id);
25592
25535
  }
25593
25536
  catch (error) {
25594
25537
  throw new Error(`Failed to fetch entity data: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -25611,5 +25554,5 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
25611
25554
  * Generated bundle index. Do not edit.
25612
25555
  */
25613
25556
 
25614
- export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, EntityBuilder, EntityDataAccessor, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, buildAXPRecordWorkflowInfo, canPersistEntityListState, cloneLayoutArrays, collectEntityQuickSearchFieldPaths, collectNestedCreateHiddenProperties, collectNestedFieldPathsFromEntityColumns, collectQuickSearchPathsFromSingleEntityDefinition, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, commandMessageTextForError, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, getRecordWorkflowCorrelationId, getRecordWorkflowInstanceId, hasFileUploaderTitleOrDescriptionFields, isAXPMiddlewareAbortError, isAttachmentListEntry, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, isUnresolvedLookupDisplayTemplate, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, mergeForeignKeyFieldIntoCreateActions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, normalizeLookupDisplayTemplate, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
25557
+ export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDataSourceKeys, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, EntityBuilder, EntityDataAccessor, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, buildAXPRecordWorkflowInfo, canPersistEntityListState, cloneLayoutArrays, collectEntityQuickSearchFieldPaths, collectNestedCreateHiddenProperties, collectNestedFieldPathsFromEntityColumns, collectQuickSearchPathsFromSingleEntityDefinition, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, commandMessageTextForError, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, getRecordWorkflowCorrelationId, getRecordWorkflowInstanceId, hasFileUploaderTitleOrDescriptionFields, isAXPMiddlewareAbortError, isAttachmentListEntry, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, mergeForeignKeyFieldIntoCreateActions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
25615
25558
  //# sourceMappingURL=acorex-platform-layout-entity.mjs.map