@acorex/platform 21.0.0-next.70 → 21.0.0-next.72

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 (55) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +10 -2
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
  4. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-common.mjs +3798 -1674
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-core.mjs +1362 -97
  8. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-builder.mjs +446 -44
  10. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-components.mjs +149 -109
  12. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
  14. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  15. package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
  16. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-layout-entity.mjs +823 -594
  18. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-views.mjs +845 -218
  20. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-widget-core.mjs +122 -33
  22. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  23. 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
  24. 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
  25. 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
  26. 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
  27. package/fesm2022/acorex-platform-layout-widgets.mjs +312 -676
  28. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  29. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  30. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  32. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-themes-default.mjs +89 -46
  34. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  35. package/fesm2022/acorex-platform-themes-shared.mjs +50 -30
  36. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  37. package/package.json +1 -1
  38. package/types/acorex-platform-auth.d.ts +2 -0
  39. package/types/acorex-platform-common.d.ts +899 -256
  40. package/types/acorex-platform-core.d.ts +394 -60
  41. package/types/acorex-platform-layout-builder.d.ts +78 -13
  42. package/types/acorex-platform-layout-components.d.ts +30 -24
  43. package/types/acorex-platform-layout-entity.d.ts +93 -44
  44. package/types/acorex-platform-layout-views.d.ts +162 -42
  45. package/types/acorex-platform-layout-widget-core.d.ts +60 -33
  46. package/types/acorex-platform-layout-widgets.d.ts +48 -20
  47. package/types/acorex-platform-themes-default.d.ts +38 -8
  48. package/types/acorex-platform-themes-shared.d.ts +6 -0
  49. package/types/acorex-platform-workflow.d.ts +1 -1
  50. package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
  51. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
  52. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
  53. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
  54. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
  55. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
@@ -2,17 +2,17 @@ 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';
9
9
  import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
10
10
  import * as i3 from '@acorex/platform/layout/widget-core';
11
11
  import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, AXPBaseWidgetComponent, AXPWidgetStatus, createStringProperty, createNumberProperty, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY, createSelectProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
12
- import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPHookService, AXPDataGenerator, AXPComponentSlotModule, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
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';
@@ -23,9 +23,9 @@ import * as i2 from '@acorex/components/popover';
23
23
  import { AXPopoverModule } from '@acorex/components/popover';
24
24
  import { AXFormatService } from '@acorex/core/format';
25
25
  import * as i5 from '@angular/common';
26
- import { CommonModule, AsyncPipe } from '@angular/common';
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) {
@@ -1686,13 +1687,14 @@ class PropertyFilter {
1686
1687
  d.setTitle(title);
1687
1688
  d.setSize(this.externalSize ?? (useFormWizard ? 'lg' : calculatedSize));
1688
1689
  d.setCloseButton(true);
1690
+ d.confirmCloseWhenDirty();
1689
1691
  d.content((layout) => {
1690
1692
  const defaultMode = this.kind === 'single' ? 'view' : 'edit';
1691
1693
  layout.mode(this.externalMode ?? defaultMode);
1692
1694
  if (useFormWizard) {
1693
1695
  layout.stepWizard((w) => {
1694
1696
  w.name(ENTITY_FORM_STEP_WIZARD_NAME);
1695
- w.setLook('arrow-minimal');
1697
+ w.setLook('rectangular-arrow-minimal');
1696
1698
  w.setShowActions(false);
1697
1699
  const mainStepTitle = (entity.formats?.individual ||
1698
1700
  entity.title ||
@@ -2497,8 +2499,7 @@ class AXPOpenEntityDetailsCommand {
2497
2499
  };
2498
2500
  }
2499
2501
  const url = `/${this.sessionService.application?.name}/m/${module}/e/${entityName}/${data.id}/view`;
2500
- // Navigate to the entity details page
2501
- this.router.navigate([url]);
2502
+ await axpNavigateAppPath(this.router, url);
2502
2503
  return {
2503
2504
  success: true,
2504
2505
  };
@@ -2554,7 +2555,9 @@ class AXPCreateEntityCommand {
2554
2555
  const entityRef = await this.entityService.resolve(moduleName, entityName);
2555
2556
  let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(data);
2556
2557
  chain.actions((actions) => {
2557
- actions.cancel('@general:actions.cancel.title').submit('@general:actions.create.title');
2558
+ actions
2559
+ .cancel('@general:actions.cancel.title')
2560
+ .submit('@general:actions.create.title', { shortcuts: ['Enter', 'ctrl+s'] });
2558
2561
  });
2559
2562
  if (excludeProperties && excludeProperties.length > 0) {
2560
2563
  chain = chain.exclude(...excludeProperties);
@@ -2969,17 +2972,6 @@ var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
2969
2972
  });
2970
2973
 
2971
2974
  //#region ---- Template resolution ----
2972
- /**
2973
- * Normalizes lookup/search display templates for row-based formatting.
2974
- * Converts `context.eval('path')` expressions to `{{ path }}` and single braces to mustache form.
2975
- */
2976
- function normalizeLookupDisplayTemplate(template) {
2977
- const withContextEval = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ $1 }}');
2978
- if (withContextEval.includes('{{')) {
2979
- return withContextEval;
2980
- }
2981
- return withContextEval.replace(/\{/g, '{{').replace(/\}/g, '}}');
2982
- }
2983
2975
  /**
2984
2976
  * Resolves the display template for a lookup item.
2985
2977
  * Priority: explicit `displayFormat` → entity `formats.lookup` (template) → entity `formats.searchResult.title`.
@@ -2988,18 +2980,18 @@ function normalizeLookupDisplayTemplate(template) {
2988
2980
  function resolveLookupDisplayTemplate(entity, options) {
2989
2981
  const explicit = options.displayFormat?.trim();
2990
2982
  if (explicit) {
2991
- return normalizeLookupDisplayTemplate(explicit);
2983
+ return normalizeEntityDisplayTemplate(explicit);
2992
2984
  }
2993
2985
  if (options.textField?.trim()) {
2994
2986
  return undefined;
2995
2987
  }
2996
2988
  const lookupFormat = entity?.formats?.lookup?.trim();
2997
2989
  if (lookupFormat && (lookupFormat.includes('{{') || lookupFormat.includes('{'))) {
2998
- return normalizeLookupDisplayTemplate(lookupFormat);
2990
+ return normalizeEntityDisplayTemplate(lookupFormat);
2999
2991
  }
3000
2992
  const searchTitle = entity?.formats?.searchResult?.title?.trim();
3001
2993
  if (searchTitle) {
3002
- return normalizeLookupDisplayTemplate(searchTitle);
2994
+ return normalizeEntityDisplayTemplate(searchTitle);
3003
2995
  }
3004
2996
  return undefined;
3005
2997
  }
@@ -3040,7 +3032,7 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
3040
3032
  const formatted = formatService.format(template, 'string', item);
3041
3033
  if (formatted) {
3042
3034
  const text = String(formatted);
3043
- if (!isUnresolvedLookupDisplayTemplate(text)) {
3035
+ if (!isUnresolvedEntityDisplayTemplate(text)) {
3044
3036
  return text;
3045
3037
  }
3046
3038
  }
@@ -3061,10 +3053,6 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
3061
3053
  }
3062
3054
  return String(raw);
3063
3055
  }
3064
- /** True when a formatted label still contains unresolved template markers. */
3065
- function isUnresolvedLookupDisplayTemplate(value) {
3066
- return /\{\{/.test(value) || /context\.eval\s*\(/.test(value);
3067
- }
3068
3056
  //#endregion
3069
3057
 
3070
3058
  class AXPEntityDetailPopoverComponent {
@@ -3104,7 +3092,7 @@ class AXPEntityDetailPopoverComponent {
3104
3092
  }
3105
3093
  }
3106
3094
  const preset = this.displayTitle()?.trim();
3107
- if (preset && !isUnresolvedLookupDisplayTemplate(preset)) {
3095
+ if (preset && !isUnresolvedEntityDisplayTemplate(preset)) {
3108
3096
  return preset;
3109
3097
  }
3110
3098
  const fallbackItem = this.item();
@@ -3203,7 +3191,7 @@ class AXPEntityDetailPopoverComponent {
3203
3191
  resolveLookupDisplayLabel(item, entityDefinition) {
3204
3192
  const label = formatLookupItemDisplay(item, entityDefinition, { textField: this.textField() }, this.formatService, (value) => this.translation.resolve(value));
3205
3193
  const resolved = typeof label === 'string' ? label : this.translation.resolve(label);
3206
- return resolved && !isUnresolvedLookupDisplayTemplate(resolved) ? resolved : '';
3194
+ return resolved && !isUnresolvedEntityDisplayTemplate(resolved) ? resolved : '';
3207
3195
  }
3208
3196
  /**
3209
3197
  * Returns true if a value is meaningful for display (non-empty/non-null).
@@ -3382,11 +3370,11 @@ class AXPEntityDetailPopoverComponent {
3382
3370
  return prop.name;
3383
3371
  }
3384
3372
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3385
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, displayTitle: { classPropertyName: "displayTitle", publicName: "displayTitle", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-text-neutral-600 ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>", 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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3373
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, displayTitle: { classPropertyName: "displayTitle", publicName: "displayTitle", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>", 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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3386
3374
  }
3387
3375
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
3388
3376
  type: Component,
3389
- args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXSkeletonModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-text-neutral-600 ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>" }]
3377
+ args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXSkeletonModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>" }]
3390
3378
  }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: true }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], displayTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayTitle", required: false }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], breadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumb", required: false }] }], detailPopover: [{ type: i0.ViewChild, args: ['detailPopover', { isSignal: true }] }] } });
3391
3379
 
3392
3380
  class AXPEntityDetailPopoverService {
@@ -3624,6 +3612,58 @@ class AXMEntityCrudServiceImpl {
3624
3612
  async custom(request) { }
3625
3613
  }
3626
3614
 
3615
+ /**
3616
+ * Error type that can be thrown by middlewares to abort the chain with a structured payload.
3617
+ */
3618
+ class AXPMiddlewareAbortError extends Error {
3619
+ constructor(message, options = {}) {
3620
+ super(message);
3621
+ this.message = message;
3622
+ this.options = options;
3623
+ this.name = 'AXPMiddlewareAbortError';
3624
+ }
3625
+ toResponse() {
3626
+ return {
3627
+ success: false,
3628
+ error: {
3629
+ code: this.options.code,
3630
+ message: this.message,
3631
+ status: this.options.status,
3632
+ details: this.options.details,
3633
+ },
3634
+ };
3635
+ }
3636
+ }
3637
+ /** Type guard for AXPMiddlewareAbortError */
3638
+ function isAXPMiddlewareAbortError(error) {
3639
+ return error instanceof AXPMiddlewareAbortError;
3640
+ }
3641
+ //#endregion
3642
+
3643
+ /**
3644
+ * Maps storage middleware failures to a command result the details view can surface.
3645
+ */
3646
+ function mapEntityStorageErrorToCommandResult(error, fallback = 'Failed to update entity') {
3647
+ if (isAXPMiddlewareAbortError(error)) {
3648
+ return {
3649
+ success: false,
3650
+ message: {
3651
+ type: 'error',
3652
+ code: error.options.code,
3653
+ text: error.message,
3654
+ details: error.options.details,
3655
+ },
3656
+ };
3657
+ }
3658
+ const text = error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
3659
+ return {
3660
+ success: false,
3661
+ message: {
3662
+ type: 'error',
3663
+ text,
3664
+ },
3665
+ };
3666
+ }
3627
3667
  /**
3628
3668
  * Best-effort plain string for thrown {@link Error} messages from command results (no i18n lookup).
3629
3669
  */
@@ -4301,6 +4341,7 @@ class AXPEntityCommandTriggerViewModel {
4301
4341
  this.hidden = action.hidden ?? false;
4302
4342
  this.disabled = action.disabled ?? false;
4303
4343
  this.default = action.default ?? false;
4344
+ this.shortcuts = action.shortcuts;
4304
4345
  this.scope = action.scope;
4305
4346
  this.order = action.order ?? 0;
4306
4347
  this.isChild = isChild;
@@ -6587,20 +6628,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6587
6628
  type: Injectable,
6588
6629
  args: [{ providedIn: 'root' }]
6589
6630
  }] });
6590
- const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory)) => {
6631
+ const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory), router = inject(Router)) => {
6591
6632
  const moduleName = route.parent?.paramMap.get('module');
6592
6633
  const entityName = route.paramMap.get('entity');
6593
- const vm = await service.create(moduleName, entityName);
6594
- // Check if filters are provided in query params
6595
- const filtersParam = route.queryParamMap.get('filters');
6596
- if (filtersParam) {
6597
- const applied = vm.applyFiltersFromQueryParams(filtersParam);
6598
- if (applied) {
6599
- // Trigger filter and sort application
6600
- await vm.applyFilterAndSort();
6634
+ try {
6635
+ const vm = await service.create(moduleName, entityName);
6636
+ // Check if filters are provided in query params
6637
+ const filtersParam = route.queryParamMap.get('filters');
6638
+ if (filtersParam) {
6639
+ const applied = vm.applyFiltersFromQueryParams(filtersParam);
6640
+ if (applied) {
6641
+ // Trigger filter and sort application
6642
+ await vm.applyFilterAndSort();
6643
+ }
6644
+ }
6645
+ return vm;
6646
+ }
6647
+ catch (error) {
6648
+ if (error instanceof AXPNotFoundError) {
6649
+ return axpRedirectToNotFound(router);
6601
6650
  }
6651
+ throw error;
6602
6652
  }
6603
- return vm;
6604
6653
  };
6605
6654
 
6606
6655
  const AXPEntityDeletedEvent = createWorkFlowEvent('[Entity] Deleted');
@@ -6651,7 +6700,7 @@ class AXPEntityPerformDeleteAction extends AXPWorkflowAction {
6651
6700
  dialog = this.loadingDialog.show({
6652
6701
  title: await this.translationService.translateAsync('@general:workflow.deleting'),
6653
6702
  mode: 'determinate',
6654
- status: 'Deleting...',
6703
+ status: await this.translationService.translateAsync('@general:workflow.deleting'),
6655
6704
  progressValue: 0,
6656
6705
  text: `0/${idLength}`,
6657
6706
  buttons: [
@@ -7412,10 +7461,19 @@ class AXPEntityPreloadFiltersViewModel {
7412
7461
  const AXPEntityPreloadFiltersViewModelResolver = async (route, state) => {
7413
7462
  const injector = inject(Injector);
7414
7463
  const entityRegistry = inject(AXPEntityDefinitionRegistryService);
7464
+ const router = inject(Router);
7415
7465
  const moduleName = route.parent?.paramMap.get('module');
7416
7466
  const entityName = route.paramMap.get('entity');
7417
- const entity = await entityRegistry.resolve(moduleName, entityName);
7418
- return new AXPEntityPreloadFiltersViewModel(injector, entity);
7467
+ try {
7468
+ const entity = await entityRegistry.resolve(moduleName, entityName);
7469
+ return new AXPEntityPreloadFiltersViewModel(injector, entity);
7470
+ }
7471
+ catch (error) {
7472
+ if (error instanceof AXPNotFoundError) {
7473
+ return axpRedirectToNotFound(router);
7474
+ }
7475
+ throw error;
7476
+ }
7419
7477
  };
7420
7478
  //#endregion
7421
7479
 
@@ -7980,7 +8038,7 @@ class AXMAttachmentsPageComponentProvider {
7980
8038
  return [
7981
8039
  {
7982
8040
  key: ATTACHMENTS_PAGE_COMPONENT_KEY,
7983
- loader: () => import('./acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs').then((m) => m.AXMAttachmentsPageComponent),
8041
+ loader: () => import('./acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs').then((m) => m.AXMAttachmentsPageComponent),
7984
8042
  },
7985
8043
  ];
7986
8044
  }
@@ -8299,7 +8357,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8299
8357
  this.noRecordsTitle = signal('', ...(ngDevMode ? [{ debugName: "noRecordsTitle" }] : /* istanbul ignore next */ []));
8300
8358
  this.currentSearchTerm = null;
8301
8359
  this.isRefreshing = false;
8302
- this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations
8360
+ this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations (multiple mode)
8303
8361
  /**
8304
8362
  * Computed property to check if we should show the "no search results" empty state.
8305
8363
  * Returns true when search is active, not searching, and no results found.
@@ -8366,9 +8424,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8366
8424
  }
8367
8425
  });
8368
8426
  }
8369
- // Mark pre-selected nodes as selected in the child nodes (for visual display)
8370
- // ONLY do this during initial load, NOT during user selection changes
8371
- if (!this.isUpdatingSelection) {
8427
+ // Mark pre-selected leaf nodes on loaded children (multiple mode only; single uses controlledSelection)
8428
+ if (!this.isUpdatingSelection && this.allowMultiple()) {
8372
8429
  const selectedIds = this.selectedNodeIds();
8373
8430
  childNodes.forEach((node) => {
8374
8431
  const nodeId = String(node['id'] ?? '');
@@ -8376,38 +8433,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8376
8433
  node['selected'] = true;
8377
8434
  }
8378
8435
  });
8379
- // After children load, programmatically select pre-selected nodes in tree component
8380
- // This ensures the tree's internal state matches our selectedNodeIds
8381
- // Skip this if we're in the middle of a selection update (user is selecting/deselecting)
8382
- const treeComponent = this.tree();
8383
- if (treeComponent && selectedIds.length > 0) {
8384
- // Use setTimeout to ensure nodes are in tree structure before selecting
8385
- setTimeout(() => {
8386
- // Double-check we're not in a selection update when timeout fires
8387
- if (this.isUpdatingSelection) {
8388
- return;
8389
- }
8390
- this.isUpdatingSelection = true;
8391
- try {
8392
- // Re-check selectedNodeIds at this point (might have changed)
8393
- const currentSelectedIds = this.selectedNodeIds();
8394
- childNodes.forEach((node) => {
8395
- const nodeId = String(node['id'] ?? '');
8396
- if (nodeId && currentSelectedIds.includes(nodeId) && nodeId !== 'all') {
8397
- try {
8398
- treeComponent.selectNode(nodeId);
8399
- }
8400
- catch {
8401
- // Node might not be in tree yet
8402
- }
8403
- }
8404
- });
8405
- }
8406
- finally {
8407
- this.isUpdatingSelection = false;
8408
- }
8409
- }, 10);
8410
- }
8411
8436
  }
8412
8437
  return childNodes;
8413
8438
  };
@@ -8433,6 +8458,60 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8433
8458
  }
8434
8459
  //#endregion
8435
8460
  //#region ---- Private Methods ----
8461
+ /** Keeps at most one id when {@link allowMultiple} is false. */
8462
+ normalizeSelectedIds(ids) {
8463
+ const filtered = ids.filter((id) => id && id !== 'all');
8464
+ if (this.allowMultiple()) {
8465
+ return [...filtered];
8466
+ }
8467
+ return filtered.length > 0 ? [filtered[filtered.length - 1]] : [];
8468
+ }
8469
+ setSelectedNodeIds(ids) {
8470
+ this.selectedNodeIds.set(this.normalizeSelectedIds(ids));
8471
+ }
8472
+ /** Keeps widget selectedNodeIds in sync when the tree uses controlled single selection. */
8473
+ onControlledSelectedIdsChange(ids) {
8474
+ if (this.allowMultiple()) {
8475
+ return;
8476
+ }
8477
+ this.setSelectedNodeIds(ids);
8478
+ }
8479
+ async waitForTreeComponent(maxWaitMs = 3500) {
8480
+ const deadline = Date.now() + maxWaitMs;
8481
+ while (Date.now() < deadline) {
8482
+ const treeComponent = this.tree();
8483
+ if (treeComponent) {
8484
+ return treeComponent;
8485
+ }
8486
+ await new Promise((resolve) => setTimeout(resolve, 80));
8487
+ }
8488
+ return undefined;
8489
+ }
8490
+ /** Expands ancestor paths and reveals the initial selection via the tree API. */
8491
+ async revealInitialSelection(ids) {
8492
+ const normalizedIds = this.normalizeSelectedIds(ids);
8493
+ if (normalizedIds.length === 0) {
8494
+ return;
8495
+ }
8496
+ const treeComponent = await this.waitForTreeComponent();
8497
+ if (!treeComponent) {
8498
+ return;
8499
+ }
8500
+ try {
8501
+ if (this.allowMultiple()) {
8502
+ await this.restoreMultipleSelectionAfterReload(normalizedIds);
8503
+ }
8504
+ else {
8505
+ const chains = await this.buildAncestorChains(normalizedIds);
8506
+ const ancestorPaths = normalizedIds.map((id) => chains.get(id) ?? []);
8507
+ await treeComponent.selectAndReveal(normalizedIds, { ancestorPaths });
8508
+ }
8509
+ }
8510
+ catch (error) {
8511
+ console.error('Error revealing initial category selection:', error);
8512
+ }
8513
+ this.changeDetectorRef.markForCheck();
8514
+ }
8436
8515
  async initializeTree() {
8437
8516
  if (!this.entityKey()) {
8438
8517
  this.loading.set(false);
@@ -8457,12 +8536,12 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8457
8536
  // Resolve initial selected IDs from data (popup) or input binding
8458
8537
  const fromData = this.selectedValues().filter((id) => id && id !== 'all');
8459
8538
  const fromInput = (this.selectedValuesInput() ?? []).filter((id) => id && id !== 'all');
8460
- const initialSelectedIds = fromData.length > 0 ? fromData : fromInput;
8539
+ const initialSelectedIds = this.normalizeSelectedIds(fromData.length > 0 ? fromData : fromInput);
8461
8540
  if (initialSelectedIds.length > 0) {
8462
8541
  await this.loadMissingNodeData(initialSelectedIds);
8463
8542
  // Re-fetch any node that has no parent info (batch query may return minimal fields)
8464
8543
  await this.ensureParentDataInCache(initialSelectedIds);
8465
- this.selectedNodeIds.set(initialSelectedIds);
8544
+ this.setSelectedNodeIds(initialSelectedIds);
8466
8545
  }
8467
8546
  }
8468
8547
  catch (error) {
@@ -8476,52 +8555,20 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8476
8555
  const selectedIds = this.selectedNodeIds();
8477
8556
  if (selectedIds.length > 0) {
8478
8557
  this.initialExpandSyncDone = true;
8479
- this.runExpandAndSyncWhenTreeReady(selectedIds);
8558
+ void this.revealInitialSelection(selectedIds);
8480
8559
  }
8481
8560
  }
8482
8561
  /**
8483
8562
  * Called when popup data arrives after ngOnInit: load data, set selection, expand and sync.
8484
8563
  */
8485
8564
  runInitialExpandAndSync(ids) {
8486
- this.selectedNodeIds.set(ids);
8565
+ this.setSelectedNodeIds(ids);
8487
8566
  Promise.resolve()
8488
8567
  .then(() => this.loadMissingNodeData(ids))
8489
8568
  .then(() => this.ensureParentDataInCache(ids))
8490
- .then(() => {
8491
- this.runExpandAndSyncWhenTreeReady(ids);
8492
- })
8569
+ .then(() => this.revealInitialSelection(ids))
8493
8570
  .catch((err) => console.error('Error in runInitialExpandAndSync:', err));
8494
8571
  }
8495
- /**
8496
- * Runs expand path + sync selection once the tree viewChild is available.
8497
- * Uses retry loop so we don't run before the view has rendered the tree.
8498
- */
8499
- runExpandAndSyncWhenTreeReady(selectedIds) {
8500
- const maxWaitMs = 3500;
8501
- const pollMs = 80;
8502
- const start = Date.now();
8503
- const run = async () => {
8504
- while (Date.now() - start < maxWaitMs) {
8505
- const treeComponent = this.tree();
8506
- if (treeComponent) {
8507
- try {
8508
- await new Promise((resolve) => setTimeout(resolve, 320));
8509
- const ancestorChains = await this.buildAncestorChains(selectedIds);
8510
- await this.expandAncestorNodesInOrder(ancestorChains);
8511
- await new Promise((resolve) => setTimeout(resolve, 120));
8512
- await this.syncSelectionWithTree(selectedIds);
8513
- }
8514
- catch (error) {
8515
- console.error('Error syncing selection after tree render:', error);
8516
- }
8517
- this.changeDetectorRef.markForCheck();
8518
- return;
8519
- }
8520
- await new Promise((resolve) => setTimeout(resolve, pollMs));
8521
- }
8522
- };
8523
- run();
8524
- }
8525
8572
  //#endregion
8526
8573
  //#region ---- Public Methods ----
8527
8574
  /**
@@ -8926,8 +8973,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8926
8973
  this.nodesExpandedDuringSearch = [];
8927
8974
  return;
8928
8975
  }
8929
- // Store current selected IDs before reload
8930
- const selectedIds = this.selectedNodeIds();
8976
+ const expandTargetIds = [...this.selectedNodeIds()];
8931
8977
  // Reload tree to show all nodes (no filtering)
8932
8978
  await treeComponent.reloadData();
8933
8979
  // Collapse nodes that were expanded during search (in reverse order - leaves first)
@@ -8947,39 +8993,38 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8947
8993
  // Clear the stored expanded nodes
8948
8994
  this.expandedNodesBeforeSearch = [];
8949
8995
  // Restore selection state after tree reload
8950
- if (selectedIds.length > 0) {
8951
- // Wait for tree to stabilize after reload
8952
- await new Promise((resolve) => setTimeout(resolve, 100));
8953
- // Re-sync selection with tree
8954
- await this.restoreSelectionAfterReload(selectedIds);
8996
+ if (expandTargetIds.length > 0) {
8997
+ if (!this.allowMultiple()) {
8998
+ await this.revealInitialSelection(expandTargetIds);
8999
+ }
9000
+ else {
9001
+ await this.restoreMultipleSelectionAfterReload(expandTargetIds);
9002
+ }
8955
9003
  }
8956
9004
  }
8957
9005
  /**
8958
- * Restores selection state after tree reload.
8959
- * Expands ancestor nodes and selects the previously selected leaf nodes.
9006
+ * Restores multiple-mode leaf selection after search reset.
8960
9007
  */
8961
- async restoreSelectionAfterReload(selectedIds) {
9008
+ async restoreMultipleSelectionAfterReload(selectedLeafIds) {
8962
9009
  const treeComponent = this.tree();
8963
- if (!treeComponent || selectedIds.length === 0) {
9010
+ const normalizedIds = this.normalizeSelectedIds(selectedLeafIds);
9011
+ if (!treeComponent || normalizedIds.length === 0) {
8964
9012
  return;
8965
9013
  }
8966
9014
  this.isUpdatingSelection = true;
8967
9015
  try {
8968
- // Build ancestor chains for selected nodes
8969
- const ancestorChains = await this.buildAncestorChains(selectedIds);
8970
- // Expand ancestor nodes to make selected nodes visible
9016
+ const ancestorChains = await this.buildAncestorChains(normalizedIds);
8971
9017
  await this.expandAncestorNodesInOrder(ancestorChains);
8972
- // Wait for tree to render expanded nodes
8973
- await new Promise((resolve) => setTimeout(resolve, 50));
8974
- // Select the nodes visually in the tree
8975
- for (const id of selectedIds) {
9018
+ for (const id of normalizedIds) {
8976
9019
  try {
8977
9020
  treeComponent.selectNode(id);
8978
9021
  }
8979
9022
  catch {
8980
- // Node might not be in tree yet
9023
+ // Node might not be loaded yet
8981
9024
  }
8982
9025
  }
9026
+ await this.updateParentStatesAfterSelection(normalizedIds);
9027
+ treeComponent.refresh();
8983
9028
  }
8984
9029
  finally {
8985
9030
  this.isUpdatingSelection = false;
@@ -8996,10 +9041,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
8996
9041
  if (!event.isUserInteraction) {
8997
9042
  return;
8998
9043
  }
8999
- // Don't process if we're already updating selection
9000
- if (this.isUpdatingSelection) {
9001
- return;
9002
- }
9003
9044
  // Cache node data for getSelectedItems (except for 'all' node)
9004
9045
  if (nodeId !== 'all') {
9005
9046
  const nodeData = node['data'];
@@ -9007,72 +9048,64 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9007
9048
  this.nodeDataCache.set(nodeId, nodeData);
9008
9049
  }
9009
9050
  }
9010
- // Check if this node is being selected or deselected
9051
+ // Single selection is handled by the tree controlledSelection + selectedIds binding.
9052
+ if (!this.allowMultiple()) {
9053
+ return;
9054
+ }
9011
9055
  const isSelected = node['selected'] === true;
9012
- if (this.allowMultiple()) {
9013
- const children = node['children'];
9014
- const childrenCount = node['childrenCount'];
9015
- // Determine if this node has children (is a parent node)
9016
- // A node has children if: childrenCount > 0, or has non-empty children array
9017
- // Note: childrenCount === undefined means "unknown" - we need to check via datasource
9018
- const hasLoadedChildren = children && children.length > 0;
9019
- const hasChildrenCount = childrenCount !== undefined && childrenCount > 0;
9020
- const isDefinitelyLeaf = childrenCount === 0 && (!children || children.length === 0);
9021
- if (isSelected) {
9022
- // SELECTION: Only add LEAF nodes to selectedNodeIds
9023
- this.isUpdatingSelection = true;
9024
- try {
9025
- if (nodeId === 'all') {
9026
- // "All Items" selected - recursively select all leaf descendants
9027
- await this.selectAllLeafDescendants(nodeId);
9028
- }
9029
- else if (isDefinitelyLeaf) {
9030
- // This is definitely a leaf node - add it directly
9056
+ const children = node['children'];
9057
+ const childrenCount = node['childrenCount'];
9058
+ // Determine if this node has children (is a parent node)
9059
+ // A node has children if: childrenCount > 0, or has non-empty children array
9060
+ // Note: childrenCount === undefined means "unknown" - we need to check via datasource
9061
+ const hasLoadedChildren = children && children.length > 0;
9062
+ const hasChildrenCount = childrenCount !== undefined && childrenCount > 0;
9063
+ const isDefinitelyLeaf = childrenCount === 0 && (!children || children.length === 0);
9064
+ if (isSelected) {
9065
+ // SELECTION: Only add LEAF nodes to selectedNodeIds
9066
+ this.isUpdatingSelection = true;
9067
+ try {
9068
+ if (nodeId === 'all') {
9069
+ // "All Items" selected - recursively select all leaf descendants
9070
+ await this.selectAllLeafDescendants(nodeId);
9071
+ }
9072
+ else if (isDefinitelyLeaf) {
9073
+ // This is definitely a leaf node - add it directly
9074
+ const currentSelected = new Set(this.selectedNodeIds());
9075
+ currentSelected.add(nodeId);
9076
+ this.setSelectedNodeIds(Array.from(currentSelected));
9077
+ }
9078
+ else if (hasLoadedChildren || hasChildrenCount) {
9079
+ // This node has children - only add its leaf descendants
9080
+ await this.selectAllLeafDescendants(nodeId);
9081
+ }
9082
+ else {
9083
+ // childrenCount is undefined - fetch once and reuse to avoid double datasource call
9084
+ const childNodes = await this.datasource(nodeId);
9085
+ if (!childNodes || childNodes.length === 0) {
9086
+ // No children - this is a leaf node, add it
9031
9087
  const currentSelected = new Set(this.selectedNodeIds());
9032
9088
  currentSelected.add(nodeId);
9033
- this.selectedNodeIds.set(Array.from(currentSelected));
9034
- }
9035
- else if (hasLoadedChildren || hasChildrenCount) {
9036
- // This node has children - only add its leaf descendants
9037
- await this.selectAllLeafDescendants(nodeId);
9089
+ this.setSelectedNodeIds(Array.from(currentSelected));
9038
9090
  }
9039
9091
  else {
9040
- // childrenCount is undefined - fetch once and reuse to avoid double datasource call
9041
- const childNodes = await this.datasource(nodeId);
9042
- if (!childNodes || childNodes.length === 0) {
9043
- // No children - this is a leaf node, add it
9044
- const currentSelected = new Set(this.selectedNodeIds());
9045
- currentSelected.add(nodeId);
9046
- this.selectedNodeIds.set(Array.from(currentSelected));
9047
- }
9048
- else {
9049
- // Has children - pass prefetched childNodes to avoid datasource(nodeId) again
9050
- await this.selectAllLeafDescendants(nodeId, childNodes);
9051
- }
9092
+ // Has children - pass prefetched childNodes to avoid datasource(nodeId) again
9093
+ await this.selectAllLeafDescendants(nodeId, childNodes);
9052
9094
  }
9053
9095
  }
9054
- finally {
9055
- this.isUpdatingSelection = false;
9056
- }
9057
9096
  }
9058
- else {
9059
- // DESELECTION: Remove node (if leaf) and all leaf descendants from selectedNodeIds
9060
- this.isUpdatingSelection = true;
9061
- try {
9062
- await this.deselectAllLeafDescendants(nodeId);
9063
- }
9064
- finally {
9065
- this.isUpdatingSelection = false;
9066
- }
9097
+ finally {
9098
+ this.isUpdatingSelection = false;
9067
9099
  }
9068
9100
  }
9069
9101
  else {
9070
- // Single selection mode: just update selectedNodeIds
9071
- if (isSelected) {
9072
- this.selectedNodeIds.set([nodeId]);
9102
+ // DESELECTION: Remove node (if leaf) and all leaf descendants from selectedNodeIds
9103
+ this.isUpdatingSelection = true;
9104
+ try {
9105
+ await this.deselectAllLeafDescendants(nodeId);
9073
9106
  }
9074
- else {
9075
- this.selectedNodeIds.set([]);
9107
+ finally {
9108
+ this.isUpdatingSelection = false;
9076
9109
  }
9077
9110
  }
9078
9111
  }
@@ -9088,7 +9121,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9088
9121
  * Clears all selected items
9089
9122
  */
9090
9123
  onClearAll() {
9091
- this.selectedNodeIds.set([]);
9124
+ this.setSelectedNodeIds([]);
9092
9125
  const treeComponent = this.tree();
9093
9126
  if (treeComponent) {
9094
9127
  treeComponent.deselectAll();
@@ -9139,7 +9172,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9139
9172
  for (const leafId of leafIds) {
9140
9173
  currentSelected.add(leafId);
9141
9174
  }
9142
- this.selectedNodeIds.set(Array.from(currentSelected));
9175
+ this.setSelectedNodeIds(Array.from(currentSelected));
9143
9176
  // Select all leaf nodes visually in the tree
9144
9177
  if (treeComponent) {
9145
9178
  for (const leafId of leafIds) {
@@ -9168,7 +9201,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9168
9201
  try {
9169
9202
  // Special case: deselecting 'all' clears everything
9170
9203
  if (parentId === 'all') {
9171
- this.selectedNodeIds.set([]);
9204
+ this.setSelectedNodeIds([]);
9172
9205
  // Use tree's deselectAll() to clear all visual selections
9173
9206
  const treeComponent = this.tree();
9174
9207
  if (treeComponent) {
@@ -9189,7 +9222,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9189
9222
  // Only update our internal state - tree handles visual state
9190
9223
  const currentSelected = this.selectedNodeIds();
9191
9224
  const newSelected = currentSelected.filter((id) => !leafIds.has(id));
9192
- this.selectedNodeIds.set(newSelected);
9225
+ this.setSelectedNodeIds(newSelected);
9193
9226
  }
9194
9227
  catch (error) {
9195
9228
  console.error(`Error deselecting leaf descendants for node ${parentId}:`, error);
@@ -9439,60 +9472,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9439
9472
  await new Promise((r) => setTimeout(r, pollMs));
9440
9473
  }
9441
9474
  }
9442
- /**
9443
- * Syncs selection state with the tree component.
9444
- * Selects leaf nodes and manually updates parent states (indeterminate/selected).
9445
- */
9446
- async syncSelectionWithTree(selectedIds) {
9447
- const treeComponent = this.tree();
9448
- if (!treeComponent || selectedIds.length === 0) {
9449
- return;
9450
- }
9451
- // Wait for tree to be fully initialized after ancestor expansion
9452
- await new Promise((resolve) => setTimeout(resolve, 200));
9453
- // Keep track of successfully synced nodes
9454
- const syncedNodes = new Set();
9455
- // Try multiple times to sync selection (nodes might load progressively)
9456
- for (let attempt = 0; attempt < 5; attempt++) {
9457
- for (const id of selectedIds) {
9458
- if (id && id !== 'all' && !syncedNodes.has(id)) {
9459
- try {
9460
- const node = treeComponent.findNode(id);
9461
- if (node) {
9462
- // Node exists in tree - mark it as selected using both methods
9463
- // 1. Direct property assignment for immediate visual feedback
9464
- node['selected'] = true;
9465
- node['indeterminate'] = false;
9466
- // 2. Use selectNode API for tree's internal state tracking
9467
- try {
9468
- treeComponent.selectNode(id);
9469
- }
9470
- catch {
9471
- // selectNode might fail but direct assignment should work
9472
- }
9473
- syncedNodes.add(id);
9474
- }
9475
- }
9476
- catch {
9477
- // Node not in tree yet
9478
- }
9479
- }
9480
- }
9481
- // If all nodes are synced, we're done
9482
- if (syncedNodes.size === selectedIds.length) {
9483
- break;
9484
- }
9485
- // Wait before next attempt
9486
- await new Promise((resolve) => setTimeout(resolve, 100));
9487
- }
9488
- // Now update parent states (indeterminate/selected) based on children
9489
- // This uses bottom-up traversal with datasource fallback for loading children
9490
- await this.updateParentStatesAfterSelection(selectedIds);
9491
- // Refresh tree to apply changes
9492
- treeComponent.refresh();
9493
- // Trigger change detection to update UI
9494
- this.changeDetectorRef.markForCheck();
9495
- }
9496
9475
  /**
9497
9476
  * Updates parent node states (selected/indeterminate) based on children selection.
9498
9477
  * Called after leaf nodes are selected to properly show parent states.
@@ -9686,35 +9665,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9686
9665
  const id = String(parentIdValue);
9687
9666
  return id && id !== 'all' ? id : null;
9688
9667
  }
9689
- /**
9690
- * Marks nodes as selected in the tree structure based on selectedNodeIds.
9691
- * This ensures pre-selected nodes appear selected when the tree is rendered.
9692
- */
9693
- markNodesAsSelected(node) {
9694
- const selectedIds = this.selectedNodeIds();
9695
- if (selectedIds.length === 0) {
9696
- return;
9697
- }
9698
- // Mark the node itself if it's selected
9699
- this.markNodeAsSelectedIfNeeded(node);
9700
- // Recursively mark children
9701
- const nodeChildren = node['children'];
9702
- if (nodeChildren) {
9703
- nodeChildren.forEach((child) => {
9704
- this.markNodesAsSelected(child);
9705
- });
9706
- }
9707
- }
9708
- /**
9709
- * Marks a single node as selected if it's in the selectedNodeIds list.
9710
- */
9711
- markNodeAsSelectedIfNeeded(node) {
9712
- const selectedIds = this.selectedNodeIds();
9713
- const nodeId = String(node['id'] ?? '');
9714
- if (nodeId && selectedIds.includes(nodeId)) {
9715
- node['selected'] = true;
9716
- }
9717
- }
9718
9668
  /**
9719
9669
  * Marks a node and its children as disabled if the node ID matches the excluded ID.
9720
9670
  */
@@ -9723,7 +9673,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9723
9673
  if (nodeId === excludedId) {
9724
9674
  node['disabled'] = true;
9725
9675
  }
9726
- // Recursively mark children
9727
9676
  const nodeChildren = node['children'];
9728
9677
  if (nodeChildren) {
9729
9678
  nodeChildren.forEach((child) => {
@@ -9732,41 +9681,16 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9732
9681
  }
9733
9682
  }
9734
9683
  /**
9735
- * Processes root node: marks excluded as disabled, marks selected, and syncs selection with tree component
9684
+ * Processes root node: marks excluded nodes as disabled.
9736
9685
  */
9737
9686
  processRootNode(rootNode) {
9738
9687
  const excludedId = this.excludedNodeId();
9739
9688
  if (excludedId) {
9740
9689
  this.markNodeAsDisabled(rootNode, excludedId);
9741
9690
  }
9742
- this.markNodesAsSelected(rootNode);
9743
- // Sync selection with tree component after a short delay to ensure tree is rendered
9744
- const treeComponent = this.tree();
9745
- if (treeComponent) {
9746
- setTimeout(() => {
9747
- this.isUpdatingSelection = true;
9748
- try {
9749
- const selectedIds = this.selectedNodeIds();
9750
- selectedIds.forEach((id) => {
9751
- if (id && id !== 'all') {
9752
- try {
9753
- treeComponent.selectNode(id);
9754
- }
9755
- catch {
9756
- // Node might not be in tree yet
9757
- }
9758
- }
9759
- });
9760
- }
9761
- finally {
9762
- this.isUpdatingSelection = false;
9763
- }
9764
- }, 0);
9765
- }
9766
9691
  }
9767
9692
  /**
9768
9693
  * Processes child nodes: marks excluded as disabled
9769
- * Selection marking is handled in datasource callback ONLY during initial load
9770
9694
  */
9771
9695
  processChildNodes(childNodes) {
9772
9696
  const excludedId = this.excludedNodeId();
@@ -9912,8 +9836,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
9912
9836
  return this.translationService.resolve(raw);
9913
9837
  }
9914
9838
  async getSelectedItems() {
9915
- // selectedNodeIds now only contains LEAF nodes (already filtered)
9916
- const selectedIds = this.selectedNodeIds();
9839
+ const selectedIds = this.normalizeSelectedIds(this.selectedNodeIds());
9917
9840
  if (selectedIds.length === 0) {
9918
9841
  return [];
9919
9842
  }
@@ -10006,6 +9929,9 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
10006
9929
  [dragBehavior]="'none'"
10007
9930
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
10008
9931
  [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
9932
+ [controlledSelection]="!allowMultiple()"
9933
+ [selectedIds]="selectedNodeIds()"
9934
+ (selectedIdsChange)="onControlledSelectedIdsChange($event)"
10009
9935
  [showIcons]="true"
10010
9936
  [titleField]="textField()"
10011
9937
  [idField]="valueField()"
@@ -10092,7 +10018,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
10092
10018
  ></ax-button>
10093
10019
  </ax-suffix>
10094
10020
  </ax-footer>
10095
- `, 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 }); }
10021
+ `, 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 }); }
10096
10022
  }
10097
10023
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityCategoryTreeSelectorComponent, decorators: [{
10098
10024
  type: Component,
@@ -10143,6 +10069,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
10143
10069
  [dragBehavior]="'none'"
10144
10070
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
10145
10071
  [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
10072
+ [controlledSelection]="!allowMultiple()"
10073
+ [selectedIds]="selectedNodeIds()"
10074
+ (selectedIdsChange)="onControlledSelectedIdsChange($event)"
10146
10075
  [showIcons]="true"
10147
10076
  [titleField]="textField()"
10148
10077
  [idField]="valueField()"
@@ -11252,9 +11181,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11252
11181
  this.isOpen.set(true);
11253
11182
  const currentValue = this.getValue();
11254
11183
  const selectedIds = currentValue
11255
- ? (this.multiple() ? castArray(currentValue) : [currentValue])
11256
- .map((v) => String(extractValue(v, this.valueField())))
11257
- .filter((id) => id && id !== 'all')
11184
+ ? this.normalizePopupSelectedIds(currentValue)
11258
11185
  : [];
11259
11186
  // Get current entity ID from context (if editing an entity)
11260
11187
  const currentEntityId = this.contextService.getValue('id');
@@ -11275,9 +11202,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11275
11202
  },
11276
11203
  });
11277
11204
  if (result?.data?.selected && Array.isArray(result.data.selected)) {
11278
- // Use the popup result directly as the source of truth
11279
- // This ensures that deselected items are properly removed
11280
- const newItems = result.data.selected;
11205
+ const newItems = this.multiple() ? result.data.selected : result.data.selected.slice(0, 1);
11281
11206
  if (newItems.length === 0) {
11282
11207
  // All items were deselected
11283
11208
  this.clear();
@@ -11426,8 +11351,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11426
11351
  setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
11427
11352
  }
11428
11353
  });
11429
- this.contextService.patch(itemToExpose, true);
11430
- // Fire triggers
11354
+ this.contextService.applyObjectPaths(itemToExpose, { origin: 'user' });
11431
11355
  this.setValue(this.singleOrMultiple(keys));
11432
11356
  }
11433
11357
  else {
@@ -11446,6 +11370,12 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
11446
11370
  singleOrMultiple(values) {
11447
11371
  return this.multiple() ? values : values[0];
11448
11372
  }
11373
+ normalizePopupSelectedIds(currentValue) {
11374
+ const ids = (this.multiple() ? castArray(currentValue) : castArray(currentValue).slice(0, 1))
11375
+ .map((v) => String(extractValue(v, this.valueField())))
11376
+ .filter((id) => id && id !== 'all');
11377
+ return this.multiple() ? ids : ids.slice(0, 1);
11378
+ }
11449
11379
  getItemLabel(item) {
11450
11380
  if (!item) {
11451
11381
  return '';
@@ -12723,11 +12653,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12723
12653
  }
12724
12654
  untracked(() => void this.applyRelatedFiltersFromContextAndDatasource(specs));
12725
12655
  }, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
12726
- /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
12656
+ /** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
12727
12657
  this.#patchDataListRefreshEffect = effect(() => {
12728
12658
  const inst = this.listWidget()?.instance;
12729
- const opts = this.options();
12730
- if (!inst?.refresh || inst.__axpEntityListRefreshPatched || !opts['syncRelatedListFiltersFromDialogContext']) {
12659
+ if (!inst?.refresh || inst.__axpEntityListRefreshPatched) {
12731
12660
  return;
12732
12661
  }
12733
12662
  inst.__axpEntityListRefreshPatched = true;
@@ -12739,6 +12668,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12739
12668
  if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
12740
12669
  await this.applyRelatedFiltersFromContextAndDatasource(specs);
12741
12670
  }
12671
+ else {
12672
+ await this.reapplyScopedFiltersToDataSource();
12673
+ }
12742
12674
  originalRefresh();
12743
12675
  })();
12744
12676
  };
@@ -12959,6 +12891,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12959
12891
  * Applies merged route + parent-scope filters to the widget value and data source.
12960
12892
  */
12961
12893
  applyMergedRouteFiltersToList() {
12894
+ void this.reapplyScopedFiltersToDataSource();
12895
+ }
12896
+ /** Syncs toolbar filters to context and AXDataSource (retries until data-list is mounted). */
12897
+ async reapplyScopedFiltersToDataSource() {
12962
12898
  const merged = this.getMergedToolbarFilters();
12963
12899
  const current = this.getValue();
12964
12900
  if (!isEqual$1(current?.toolbar?.filters, merged)) {
@@ -12967,7 +12903,26 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12967
12903
  toolbar: { ...(current?.toolbar ?? {}), filters: merged },
12968
12904
  });
12969
12905
  }
12906
+ const pushed = await this.ensureToolbarFiltersOnDataSource();
12907
+ if (pushed) {
12908
+ this.triggerInitialLoadIfNeeded();
12909
+ }
12910
+ }
12911
+ /**
12912
+ * Entity lists use `fetchDataMode: 'manual'`; after scoped filters reach the data source,
12913
+ * the grid must be refreshed once (initial #effect may run before the data-list is ready).
12914
+ */
12915
+ triggerInitialLoadIfNeeded() {
12916
+ if (this.isMounted()) {
12917
+ return;
12918
+ }
12919
+ const listInstance = this.listWidget()?.instance;
12920
+ if (!listInstance?.call) {
12921
+ return;
12922
+ }
12970
12923
  this.pushToolbarFiltersToDataSource();
12924
+ listInstance.call('refresh');
12925
+ this.isMounted.set(true);
12971
12926
  }
12972
12927
  /**
12973
12928
  * Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
@@ -13014,26 +12969,29 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13014
12969
  return true;
13015
12970
  }
13016
12971
  /**
13017
- * Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
12972
+ * Pushes toolbar filters once the embedded data-list exposes its data source
12973
+ * (listNode is created via deferred setTimeout in ngOnInit).
13018
12974
  */
13019
- async applyRelatedFiltersFromContextAndDatasource(specs) {
13020
- await this.applyRelatedFiltersFromContext(specs);
12975
+ async ensureToolbarFiltersOnDataSource() {
13021
12976
  if (this.pushToolbarFiltersToDataSource()) {
13022
- return;
13023
- }
13024
- const opts = this.options();
13025
- if (!opts['syncRelatedListFiltersFromDialogContext']) {
13026
- return;
12977
+ return true;
13027
12978
  }
13028
- /** Data-list is created in ngOnInit via deferred listNode.set; retry briefly until instance exposes dataSource. */
13029
12979
  const maxAttempts = 40;
13030
12980
  const delayMs = 50;
13031
12981
  for (let i = 0; i < maxAttempts; i++) {
13032
12982
  await new Promise((resolve) => setTimeout(resolve, delayMs));
13033
12983
  if (this.pushToolbarFiltersToDataSource()) {
13034
- return;
12984
+ return true;
13035
12985
  }
13036
12986
  }
12987
+ return false;
12988
+ }
12989
+ /**
12990
+ * Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
12991
+ */
12992
+ async applyRelatedFiltersFromContextAndDatasource(specs) {
12993
+ await this.applyRelatedFiltersFromContext(specs);
12994
+ await this.ensureToolbarFiltersOnDataSource();
13037
12995
  }
13038
12996
  /**
13039
12997
  * Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
@@ -13041,7 +12999,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13041
12999
  refreshGridWithParentScopedFilters() {
13042
13000
  this.listWidget()?.instance?.call('refresh');
13043
13001
  }
13044
- /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
13002
+ /** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
13045
13003
  #patchDataListRefreshEffect;
13046
13004
  #effect;
13047
13005
  /**
@@ -13056,7 +13014,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13056
13014
  handleQueryChanges(queries, dataSource, listInstance, isMounted) {
13057
13015
  const changeTracker = this.analyzeChanges(queries);
13058
13016
  this.previousQueries = queries;
13059
- this.applyDataSourceChanges(dataSource, queries, changeTracker);
13017
+ this.applyDataSourceChanges(dataSource, queries, changeTracker, isMounted);
13060
13018
  this.handleListRefresh(listInstance, changeTracker, isMounted);
13061
13019
  this.handleColumnChanges(changeTracker);
13062
13020
  }
@@ -13072,30 +13030,40 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13072
13030
  hasAnyChange: changes.length > 0,
13073
13031
  };
13074
13032
  }
13033
+ /**
13034
+ * Maps toolbar sort queries ({ name, dir }) to AXDataSource sort options ({ field, dir }).
13035
+ */
13036
+ toDataSourceSortOptions(sorts) {
13037
+ return (sorts ?? [])
13038
+ .filter((s) => s.dir)
13039
+ .map((s) => ({ field: s.name, dir: s.dir }));
13040
+ }
13075
13041
  /**
13076
13042
  * Applies filter and sort changes to the data source
13077
13043
  */
13078
- applyDataSourceChanges(dataSource, queries, changeTracker) {
13079
- if (changeTracker.isFilterChanged) {
13044
+ applyDataSourceChanges(dataSource, queries, changeTracker, isMounted) {
13045
+ const shouldApplyFilters = changeTracker.isFilterChanged || (!isMounted && (queries?.filters?.length ?? 0) > 0);
13046
+ if (shouldApplyFilters) {
13080
13047
  dataSource.filter({
13081
13048
  filters: queries?.filters,
13082
13049
  });
13083
13050
  }
13084
13051
  if (changeTracker.isSortChanged) {
13085
- dataSource.sort(...(queries.sorts || []));
13052
+ const sortOptions = this.toDataSourceSortOptions(queries.sorts);
13053
+ dataSource.sort(...sortOptions);
13086
13054
  }
13087
13055
  }
13088
13056
  /**
13089
- * Handles list refresh logic based on changes and mount status
13057
+ * Handles list refresh logic based on changes and mount status.
13058
+ * Initial fetch is owned by {@link triggerInitialLoadIfNeeded} (manual fetchDataMode).
13090
13059
  */
13091
13060
  handleListRefresh(listInstance, changeTracker, isMounted) {
13092
- const shouldRefresh = changeTracker.isFilterChanged || changeTracker.isSortChanged || !isMounted;
13093
- if (shouldRefresh) {
13061
+ if (!isMounted) {
13062
+ return;
13063
+ }
13064
+ if (changeTracker.isFilterChanged || changeTracker.isSortChanged) {
13065
+ this.pushToolbarFiltersToDataSource();
13094
13066
  listInstance.call('refresh');
13095
- // Set mounted flag only on initial load (not when filters/sorts change)
13096
- if (!changeTracker.isFilterChanged && !changeTracker.isSortChanged) {
13097
- this.isMounted.set(true);
13098
- }
13099
13067
  }
13100
13068
  }
13101
13069
  /**
@@ -15534,7 +15502,8 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15534
15502
  this.setLoading = (loading) => {
15535
15503
  this.isLoading.set(loading);
15536
15504
  };
15537
- this.setItems = (items) => {
15505
+ /** @param syncContext When false, only updates UI (hydration/revert); skips context writes. */
15506
+ this.setItems = (items, syncContext = true) => {
15538
15507
  // Ensure items is always an array
15539
15508
  items = castArray(items);
15540
15509
  // Filter out null/undefined items
@@ -15550,11 +15519,11 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15550
15519
  return item;
15551
15520
  });
15552
15521
  this.selectedItems.set(items);
15553
- //
15522
+ if (!syncContext) {
15523
+ return;
15524
+ }
15554
15525
  const keys = items.map((item) => get(item, this.valueField()));
15555
15526
  const text = items.map((item) => this.mlsResolver.resolve(get(item, displayField)));
15556
- //
15557
- // extract data from valueField and set context by expose path
15558
15527
  if (this.expose()) {
15559
15528
  this.expoesItems();
15560
15529
  }
@@ -15583,7 +15552,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15583
15552
  ? castArray(this.filterMode() ? rawValue.value : rawValue)
15584
15553
  : [rawValue].filter((v) => v != null);
15585
15554
  if (!values.length) {
15586
- this.setItems([]);
15555
+ this.setItems([], false);
15587
15556
  this.isLoading.set(false);
15588
15557
  return;
15589
15558
  }
@@ -15598,22 +15567,22 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15598
15567
  .map((id) => entityDataAccessor.byKey(id)));
15599
15568
  // Filter out null/undefined results
15600
15569
  const validItems = items.filter((item) => item != null);
15601
- this.setItems(validItems);
15570
+ this.setItems(validItems, false);
15602
15571
  }
15603
15572
  else {
15604
15573
  const id = extractValue(this.filterMode() ? values[0].value : values[0], this.valueField());
15605
15574
  if (id != null) {
15606
15575
  const item = await entityDataAccessor.byKey(id);
15607
- this.setItems(item != null ? [item] : []);
15576
+ this.setItems(item != null ? [item] : [], false);
15608
15577
  }
15609
15578
  else {
15610
- this.setItems([]);
15579
+ this.setItems([], false);
15611
15580
  }
15612
15581
  }
15613
15582
  }
15614
15583
  catch (error) {
15615
15584
  console.error('[LookupWidget] findByValue() error:', error);
15616
- this.setItems([]);
15585
+ this.setItems([], false);
15617
15586
  }
15618
15587
  finally {
15619
15588
  this.isLoading.set(false);
@@ -16121,7 +16090,7 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
16121
16090
  if (!resolvedTitle) {
16122
16091
  const displayTitle = formatLookupItemDisplay(headerItem, this.entityDef(), this.lookupDisplayOptions(), this.formatService, (value) => this.translation.resolve(value));
16123
16092
  resolvedTitle = typeof displayTitle === 'string' ? displayTitle : this.translation.resolve(displayTitle);
16124
- if (isUnresolvedLookupDisplayTemplate(resolvedTitle)) {
16093
+ if (isUnresolvedEntityDisplayTemplate(resolvedTitle)) {
16125
16094
  resolvedTitle = '';
16126
16095
  }
16127
16096
  }
@@ -16516,11 +16485,11 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
16516
16485
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) || /^\d+$/.test(value);
16517
16486
  }
16518
16487
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16519
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"detailPopoverEnabled()\"\n [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"detailPopoverEnabled()\"\n [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16488
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\" #lazyTrigger>\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handlePopoverItemClick($index)\">\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>", dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16520
16489
  }
16521
16490
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, decorators: [{
16522
16491
  type: Component,
16523
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"detailPopoverEnabled()\"\n [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"detailPopoverEnabled()\"\n [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n" }]
16492
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\" #lazyTrigger>\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handlePopoverItemClick($index)\">\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>" }]
16524
16493
  }], ctorParameters: () => [], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], lazyTrigger: [{ type: i0.ViewChild, args: ['lazyTrigger', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
16525
16494
 
16526
16495
  var lookupWidgetColumn_component = /*#__PURE__*/Object.freeze({
@@ -18320,6 +18289,14 @@ class AXPFileListComponent {
18320
18289
  /** When true, edit dialog shows name, title and description fields. Default false. */
18321
18290
  this.enableTitleDescription = input(false, ...(ngDevMode ? [{ debugName: "enableTitleDescription" }] : /* istanbul ignore next */ []));
18322
18291
  this.multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
18292
+ /** `rows`: file-type icons and action buttons; `links`: compact paperclip links (download on click). */
18293
+ this.look = input('rows', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
18294
+ /** i18n key for links look label (template adds trailing colon). */
18295
+ this.titleKey = input('@general:widgets.file-uploader.attachments.title', ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
18296
+ /** When false, links look omits the label (e.g. popover body). */
18297
+ this.showLabel = input(true, ...(ngDevMode ? [{ debugName: "showLabel" }] : /* istanbul ignore next */ []));
18298
+ /** `inline`: label + divider; `menu`: compact list for popovers/panels. */
18299
+ this.linksLayout = input('inline', ...(ngDevMode ? [{ debugName: "linksLayout" }] : /* istanbul ignore next */ []));
18323
18300
  this.files = input([], ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
18324
18301
  // Plugin context (passed from parent widget)
18325
18302
  this.plugins = input(undefined, ...(ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []));
@@ -18330,11 +18307,19 @@ class AXPFileListComponent {
18330
18307
  * All files should be displayed, even those with `deleted` status.
18331
18308
  * The template will handle the visual differences based on the status.
18332
18309
  */
18333
- this.displayFiles = computed(() => this.files(), ...(ngDevMode ? [{ debugName: "displayFiles" }] : /* istanbul ignore next */ []));
18310
+ this.displayFiles = computed(() => {
18311
+ const list = this.files();
18312
+ if (this.look() === 'links') {
18313
+ return list.filter((file) => file.status !== 'deleted');
18314
+ }
18315
+ return list;
18316
+ }, ...(ngDevMode ? [{ debugName: "displayFiles" }] : /* istanbul ignore next */ []));
18334
18317
  // Cache for per-file actions provided by hooks
18335
18318
  this.fileIdToActions = signal({}, ...(ngDevMode ? [{ debugName: "fileIdToActions" }] : /* istanbul ignore next */ []));
18336
18319
  // Global actions from FileUploaderWebhookKeys.Actions hook
18337
18320
  this.globalActions = signal([], ...(ngDevMode ? [{ debugName: "globalActions" }] : /* istanbul ignore next */ []));
18321
+ /** Suppress duplicate downloads from double-click (two click events). */
18322
+ this.recentDownloadAtByKey = new Map();
18338
18323
  // Clear cache when files change to reload actions
18339
18324
  this.filesChangeEffect = effect(() => {
18340
18325
  // Track files changes to clear cache
@@ -18342,6 +18327,7 @@ class AXPFileListComponent {
18342
18327
  this.fileIdToActions.set({});
18343
18328
  }, ...(ngDevMode ? [{ debugName: "filesChangeEffect" }] : /* istanbul ignore next */ []));
18344
18329
  }
18330
+ static { this.DOWNLOAD_DEBOUNCE_MS = 500; }
18345
18331
  // Default actions that can be removed via hooks
18346
18332
  getDefaultActions(file) {
18347
18333
  const defaultActions = [];
@@ -18438,6 +18424,10 @@ class AXPFileListComponent {
18438
18424
  this.fileIdToActions.set(next);
18439
18425
  }
18440
18426
  async ngOnInit() {
18427
+ if (this.look() === 'links') {
18428
+ this.isLoading.set(false);
18429
+ return;
18430
+ }
18441
18431
  this.fileTypes.set(await this.fileTypeService.items());
18442
18432
  await this.loadGlobalActions();
18443
18433
  this.isLoading.set(false);
@@ -18457,6 +18447,36 @@ class AXPFileListComponent {
18457
18447
  this.globalActions.set([]);
18458
18448
  }
18459
18449
  }
18450
+ shouldSkipDuplicateDownload(file) {
18451
+ const key = this.downloadDedupeKey(file);
18452
+ const now = Date.now();
18453
+ const last = this.recentDownloadAtByKey.get(key);
18454
+ if (last != null && now - last < AXPFileListComponent.DOWNLOAD_DEBOUNCE_MS) {
18455
+ return true;
18456
+ }
18457
+ this.recentDownloadAtByKey.set(key, now);
18458
+ return false;
18459
+ }
18460
+ downloadDedupeKey(file) {
18461
+ if (typeof file.id === 'string' && file.id.trim()) {
18462
+ return file.id;
18463
+ }
18464
+ const src = file.source;
18465
+ if (src?.kind === 'fileId' && typeof src.value === 'string' && src.value.trim()) {
18466
+ return src.value;
18467
+ }
18468
+ return file.name;
18469
+ }
18470
+ /** Ensures `source` is set for links look and legacy rows that only carry storage id on `id`. */
18471
+ resolveFileForDownload(file) {
18472
+ if (file.source) {
18473
+ return file;
18474
+ }
18475
+ if (typeof file.id === 'string' && file.id.trim()) {
18476
+ return { ...file, source: { kind: 'fileId', value: file.id } };
18477
+ }
18478
+ return file;
18479
+ }
18460
18480
  getFileInfo(fileName) {
18461
18481
  const extension = fileName.split('.').pop()?.toLowerCase() || '';
18462
18482
  const extensions = this.fileTypes().flatMap((t) => t.extensions);
@@ -18466,10 +18486,23 @@ class AXPFileListComponent {
18466
18486
  type: fileType?.name || 'Unknown',
18467
18487
  };
18468
18488
  }
18489
+ /**
18490
+ * Double-click on a file row (rows look) or link row triggers download.
18491
+ */
18492
+ handleFileRowActivate(event, file) {
18493
+ if (file.status === 'deleted') {
18494
+ return;
18495
+ }
18496
+ void this.handleFileDownload(event, file);
18497
+ }
18469
18498
  async handleFileDownload(event, file) {
18470
- event.nativeEvent.preventDefault();
18471
- event.nativeEvent.stopPropagation();
18472
- try {
18499
+ const nativeEvent = event instanceof Event ? event : event.nativeEvent;
18500
+ nativeEvent.preventDefault();
18501
+ nativeEvent.stopPropagation();
18502
+ if (this.shouldSkipDuplicateDownload(file)) {
18503
+ return;
18504
+ }
18505
+ try {
18473
18506
  await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.BeforeDownload, {
18474
18507
  file,
18475
18508
  plugins: this.plugins() ?? [],
@@ -18478,7 +18511,9 @@ class AXPFileListComponent {
18478
18511
  });
18479
18512
  }
18480
18513
  catch { }
18481
- if (!file.source) {
18514
+ const resolved = this.resolveFileForDownload(file);
18515
+ const source = resolved.source;
18516
+ if (!source) {
18482
18517
  console.error('File source is undefined, cannot download.', file);
18483
18518
  return;
18484
18519
  }
@@ -18492,37 +18527,37 @@ class AXPFileListComponent {
18492
18527
  document.body.removeChild(link);
18493
18528
  URL.revokeObjectURL(url);
18494
18529
  };
18495
- switch (file.source.kind) {
18530
+ switch (source.kind) {
18496
18531
  case 'blob':
18497
- if (file.source.value instanceof Blob) {
18498
- triggerDownload(file.source.value, file.name ?? 'download');
18532
+ if (source.value instanceof Blob) {
18533
+ triggerDownload(source.value, resolved.name ?? 'download');
18499
18534
  }
18500
18535
  else {
18501
18536
  console.error('Source kind is blob, but value is not a Blob.', file);
18502
18537
  }
18503
18538
  break;
18504
18539
  case 'fileId':
18505
- if (typeof file.source.value === 'string') {
18540
+ if (typeof source.value === 'string') {
18506
18541
  try {
18507
- const fileInfo = await this.fileStorageService.getInfo(file.source.value);
18542
+ const fileInfo = await this.fileStorageService.getInfo(source.value);
18508
18543
  if (fileInfo && fileInfo.url) {
18509
18544
  const link = document.createElement('a');
18510
18545
  link.href = fileInfo.url;
18511
- link.download = file.name ?? fileInfo.name ?? 'download';
18546
+ link.download = resolved.name ?? fileInfo.name ?? 'download';
18512
18547
  link.target = '_blank';
18513
18548
  document.body.appendChild(link);
18514
18549
  link.click();
18515
18550
  document.body.removeChild(link);
18516
18551
  }
18517
18552
  else if (fileInfo && fileInfo.binary instanceof Blob) {
18518
- triggerDownload(fileInfo.binary, file.name ?? fileInfo.name ?? 'download');
18553
+ triggerDownload(fileInfo.binary, resolved.name ?? fileInfo.name ?? 'download');
18519
18554
  }
18520
18555
  else {
18521
18556
  console.error('Could not retrieve file for download from fileId:', fileInfo);
18522
18557
  }
18523
18558
  }
18524
18559
  catch (error) {
18525
- console.error('Error downloading file by fileId:', file.source.value, error);
18560
+ console.error('Error downloading file by fileId:', source.value, error);
18526
18561
  }
18527
18562
  }
18528
18563
  else {
@@ -18530,10 +18565,10 @@ class AXPFileListComponent {
18530
18565
  }
18531
18566
  break;
18532
18567
  case 'url':
18533
- if (typeof file.source.value === 'string') {
18568
+ if (typeof source.value === 'string') {
18534
18569
  const link = document.createElement('a');
18535
- link.href = file.source.value;
18536
- link.download = file.name ?? 'download';
18570
+ link.href = source.value;
18571
+ link.download = resolved.name ?? 'download';
18537
18572
  link.target = '_blank';
18538
18573
  document.body.appendChild(link);
18539
18574
  link.click();
@@ -18544,14 +18579,14 @@ class AXPFileListComponent {
18544
18579
  }
18545
18580
  break;
18546
18581
  case 'reference':
18547
- if (file.source.value &&
18548
- typeof file.source.value === 'object' &&
18549
- 'id' in file.source.value &&
18550
- 'type' in file.source.value) {
18582
+ if (source.value &&
18583
+ typeof source.value === 'object' &&
18584
+ 'id' in source.value &&
18585
+ 'type' in source.value) {
18551
18586
  try {
18552
18587
  const result = await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.DownloadReference, {
18553
- file,
18554
- reference: file.source.value,
18588
+ file: resolved,
18589
+ reference: source.value,
18555
18590
  plugins: this.plugins() ?? [],
18556
18591
  excludePlugins: this.excludePlugins() ?? [],
18557
18592
  capabilities: this.capabilities(),
@@ -18561,14 +18596,14 @@ class AXPFileListComponent {
18561
18596
  if (result.fileInfo.url) {
18562
18597
  const link = document.createElement('a');
18563
18598
  link.href = result.fileInfo.url;
18564
- link.download = file.name ?? result.fileInfo.name ?? 'download';
18599
+ link.download = resolved.name ?? result.fileInfo.name ?? 'download';
18565
18600
  link.target = '_blank';
18566
18601
  document.body.appendChild(link);
18567
18602
  link.click();
18568
18603
  document.body.removeChild(link);
18569
18604
  }
18570
18605
  else if (result.fileInfo.binary instanceof Blob) {
18571
- triggerDownload(result.fileInfo.binary, file.name ?? result.fileInfo.name ?? 'download');
18606
+ triggerDownload(result.fileInfo.binary, resolved.name ?? result.fileInfo.name ?? 'download');
18572
18607
  }
18573
18608
  else {
18574
18609
  console.error('Could not retrieve file for download from reference:', result.fileInfo);
@@ -18579,7 +18614,7 @@ class AXPFileListComponent {
18579
18614
  }
18580
18615
  }
18581
18616
  catch (error) {
18582
- console.error('Error downloading file by reference:', file.source.value, error);
18617
+ console.error('Error downloading file by reference:', source.value, error);
18583
18618
  }
18584
18619
  }
18585
18620
  else {
@@ -18589,12 +18624,12 @@ class AXPFileListComponent {
18589
18624
  case 'preview':
18590
18625
  case 'none':
18591
18626
  default:
18592
- console.error(`Download not supported for source kind: ${file.source.kind}`, file);
18627
+ console.error(`Download not supported for source kind: ${source.kind}`, file);
18593
18628
  break;
18594
18629
  }
18595
18630
  try {
18596
18631
  await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.AfterDownload, {
18597
- file,
18632
+ file: resolved,
18598
18633
  plugins: this.plugins() ?? [],
18599
18634
  excludePlugins: this.excludePlugins() ?? [],
18600
18635
  capabilities: this.capabilities(),
@@ -18663,7 +18698,9 @@ class AXPFileListComponent {
18663
18698
  }
18664
18699
  catch { }
18665
18700
  }
18666
- ngOnDestroy() { }
18701
+ ngOnDestroy() {
18702
+ this.recentDownloadAtByKey.clear();
18703
+ }
18667
18704
  async runAction(action) {
18668
18705
  try {
18669
18706
  await action.run(this.capabilities());
@@ -18687,19 +18724,24 @@ class AXPFileListComponent {
18687
18724
  return action.color ?? 'primary';
18688
18725
  }
18689
18726
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
18690
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileListComponent, isStandalone: true, selector: "axp-file-list", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, fileEditable: { classPropertyName: "fileEditable", publicName: "fileEditable", isSignal: true, isRequired: false, transformFunction: null }, enableTitleDescription: { classPropertyName: "enableTitleDescription", publicName: "enableTitleDescription", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null }, excludePlugins: { classPropertyName: "excludePlugins", publicName: "excludePlugins", isSignal: true, isRequired: false, transformFunction: null }, capabilities: { classPropertyName: "capabilities", publicName: "capabilities", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onRemove: "onRemove", onRevert: "onRevert", onRename: "onRename" }, providers: [], ngImport: i0, template: "@for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n }\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }} fa-solid\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <!-- Revert button - only show when multiple is true -->\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event,file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n <!-- Default + hook actions; readonly still shows download (edit/remove omitted in component logic). -->\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n} @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n}\n", styles: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem;padding-top:.5rem;padding-bottom:.5rem}:host .__item{display:flex;cursor:pointer;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"], dependencies: [{ kind: "ngmodule", type:
18691
- // Angular
18692
- CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
18727
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileListComponent, isStandalone: true, selector: "axp-file-list", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, fileEditable: { classPropertyName: "fileEditable", publicName: "fileEditable", isSignal: true, isRequired: false, transformFunction: null }, enableTitleDescription: { classPropertyName: "enableTitleDescription", publicName: "enableTitleDescription", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, linksLayout: { classPropertyName: "linksLayout", publicName: "linksLayout", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null }, excludePlugins: { classPropertyName: "excludePlugins", publicName: "excludePlugins", isSignal: true, isRequired: false, transformFunction: null }, capabilities: { classPropertyName: "capabilities", publicName: "capabilities", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onRemove: "onRemove", onRevert: "onRevert", onRename: "onRename" }, host: { properties: { "class.--look-rows": "look() === \"rows\"", "class.--look-links": "look() === \"links\"", "class.--links-inline": "look() === \"links\" && linksLayout() === \"inline\"" } }, providers: [], ngImport: i0, template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip ax-flex-shrink-0\"></i>\n <span class=\"ax-min-w-0 ax-break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }} fa-solid\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem}:host.--look-rows{padding-top:.5rem;padding-bottom:.5rem}:host.--look-links{padding-top:0;padding-bottom:0}:host.--look-links .__links .__links-label{margin-inline-end:.5rem;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:.25rem;list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:.75rem;border-radius:.375rem;border-width:0px;background-color:transparent;padding:.625rem .75rem;text-align:start;font-size:.875rem;line-height:1.25rem;text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:1rem;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-surface),var(--tw-border-opacity, 1));padding-bottom:1rem}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:.5rem}:host .__item{display:flex;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
18693
18728
  // ACoreX
18694
18729
  AXFormModule }, { kind: "ngmodule", type: AXTextBoxModule }, { 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: AXLabelModule }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type:
18695
18730
  // Platform
18696
- AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }] }); }
18731
+ AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "pipe", type:
18732
+ // Angular
18733
+ AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }] }); }
18697
18734
  }
18698
18735
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, decorators: [{
18699
18736
  type: Component,
18700
- args: [{ selector: 'axp-file-list', imports: [
18737
+ args: [{ selector: 'axp-file-list', host: {
18738
+ '[class.--look-rows]': 'look() === "rows"',
18739
+ '[class.--look-links]': 'look() === "links"',
18740
+ '[class.--links-inline]': 'look() === "links" && linksLayout() === "inline"',
18741
+ }, imports: [
18701
18742
  // Angular
18702
- CommonModule,
18743
+ AsyncPipe,
18744
+ NgClass,
18703
18745
  // ACoreX
18704
18746
  AXFormModule,
18705
18747
  AXTextBoxModule,
@@ -18710,8 +18752,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
18710
18752
  AXTranslationModule,
18711
18753
  // Platform
18712
18754
  AXPStateMessageComponent,
18713
- ], providers: [], template: "@for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n }\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }} fa-solid\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <!-- Revert button - only show when multiple is true -->\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event,file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n <!-- Default + hook actions; readonly still shows download (edit/remove omitted in component logic). -->\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n} @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n}\n", styles: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem;padding-top:.5rem;padding-bottom:.5rem}:host .__item{display:flex;cursor:pointer;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"] }]
18714
- }], propDecorators: { onRemove: [{ type: i0.Output, args: ["onRemove"] }], onRevert: [{ type: i0.Output, args: ["onRevert"] }], onRename: [{ type: i0.Output, args: ["onRename"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], fileEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileEditable", required: false }] }], enableTitleDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTitleDescription", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], excludePlugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludePlugins", required: false }] }], capabilities: [{ type: i0.Input, args: [{ isSignal: true, alias: "capabilities", required: false }] }] } });
18755
+ ], providers: [], template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip ax-flex-shrink-0\"></i>\n <span class=\"ax-min-w-0 ax-break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }} fa-solid\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem}:host.--look-rows{padding-top:.5rem;padding-bottom:.5rem}:host.--look-links{padding-top:0;padding-bottom:0}:host.--look-links .__links .__links-label{margin-inline-end:.5rem;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:.25rem;list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:.75rem;border-radius:.375rem;border-width:0px;background-color:transparent;padding:.625rem .75rem;text-align:start;font-size:.875rem;line-height:1.25rem;text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:1rem;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-surface),var(--tw-border-opacity, 1));padding-bottom:1rem}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:.5rem}:host .__item{display:flex;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"] }]
18756
+ }], propDecorators: { onRemove: [{ type: i0.Output, args: ["onRemove"] }], onRevert: [{ type: i0.Output, args: ["onRevert"] }], onRename: [{ type: i0.Output, args: ["onRename"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], fileEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileEditable", required: false }] }], enableTitleDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTitleDescription", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], linksLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "linksLayout", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], excludePlugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludePlugins", required: false }] }], capabilities: [{ type: i0.Input, args: [{ isSignal: true, alias: "capabilities", required: false }] }] } });
18715
18757
 
18716
18758
  //#region ---- Attachment fingerprint (no blob comparison) ----
18717
18759
  /** Stable fingerprint for one file row; ignores Blob binary content. */
@@ -18968,95 +19010,128 @@ function hasFileUploaderTitleOrDescriptionFields(editDialog) {
18968
19010
  }
18969
19011
  //#endregion
18970
19012
 
18971
- class AXPFileUploaderWidgetService {
18972
- constructor() {
18973
- //#region ---- Services & Dependencies ----
18974
- this.popupService = inject(AXPopupService);
18975
- this.translate = inject(AXTranslationService);
18976
- this.commandExecutor = inject(AXPCommandExecutor);
18977
- this.queryExecutor = inject(AXPQueryExecutor);
19013
+ //#region ---- Imports ----
19014
+ //#endregion
19015
+ //#region ---- Title template resolution ----
19016
+ const INVALID_DISPLAY_TEXT = '[object Object]';
19017
+ /** Resolves a row field or format result to a plain display string for the active locale. */
19018
+ function resolveEntityRowDisplayText(value, activeLang) {
19019
+ if (value == null || value === '') {
19020
+ return '';
18978
19021
  }
18979
- //#endregion
18980
- //#region ---- Public API ----
18981
- async showFileList(options) {
18982
- const entity = options?.entity;
18983
- let files = options?.files;
18984
- if (entity) {
18985
- const filesResult = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
18986
- if (!filesResult) {
18987
- return undefined;
18988
- }
18989
- files = filesResult.files ?? [];
19022
+ if (typeof value === 'string') {
19023
+ const text = value.trim();
19024
+ if (!text || text === INVALID_DISPLAY_TEXT || isUnresolvedEntityDisplayTemplate(text)) {
19025
+ return '';
18990
19026
  }
18991
- const resolved = {
18992
- files: files ?? [],
18993
- readonly: options?.readonly ?? false,
18994
- multiple: options?.multiple ?? false,
18995
- accept: options?.accept ?? '*',
18996
- fileEditable: options?.fileEditable ?? true,
18997
- maxFileSize: options?.maxFileSize ?? 1024 * 1024 * 10,
18998
- showAddItemButton: options?.showAddItemButton ?? true,
18999
- plugins: options?.plugins ?? [],
19000
- editDialog: options?.editDialog,
19001
- };
19002
- const component = await import('./acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs').then((m) => m.AXPFileListPopupComponent);
19003
- const result = await this.popupService.open(component, {
19004
- title: await this.translate.translateAsync('@document-management:terms.common.file'),
19005
- data: {
19006
- files: signal(resolved.files),
19007
- isReadonly: signal(resolved.readonly),
19008
- multiple: signal(resolved.multiple),
19009
- accept: signal(resolved.accept),
19010
- maxFileSize: signal(resolved.maxFileSize),
19011
- fileEditable: signal(resolved.fileEditable),
19012
- showAddItemButton: signal(resolved.showAddItemButton),
19013
- plugins: signal(resolved.plugins),
19014
- editDialog: signal(resolved.editDialog),
19015
- },
19016
- });
19017
- const updatedFiles = result?.data?.data?.files;
19018
- if (!updatedFiles) {
19019
- return undefined;
19027
+ return text;
19028
+ }
19029
+ if (typeof value === 'number' || typeof value === 'boolean') {
19030
+ return String(value);
19031
+ }
19032
+ if (typeof value === 'object' && !Array.isArray(value)) {
19033
+ const text = resolveMultiLanguageString(value, activeLang).trim();
19034
+ if (!text || text === INVALID_DISPLAY_TEXT) {
19035
+ return '';
19020
19036
  }
19021
- if (!entity) {
19022
- return updatedFiles;
19037
+ return text;
19038
+ }
19039
+ return '';
19040
+ }
19041
+ /**
19042
+ * Formats the entity row title for display using entity-driven templates and property paths.
19043
+ */
19044
+ function formatEntityRowDisplayTitle(entity, rowData, formatService, activeLang) {
19045
+ if (!rowData) {
19046
+ return '';
19047
+ }
19048
+ const titleContext = buildEntitySearchTitleContext(entity ?? {});
19049
+ for (const path of titleContext.fallbackFields) {
19050
+ const fromField = resolveEntityRowDisplayText(get(rowData, path), activeLang);
19051
+ if (fromField) {
19052
+ return fromField;
19023
19053
  }
19024
- const saveResult = await this.commandExecutor.execute('FileUploader:SaveFiles', {
19025
- ...entity,
19026
- files: updatedFiles,
19027
- });
19028
- if (!saveResult?.success) {
19029
- return undefined;
19054
+ }
19055
+ const templates = [
19056
+ resolveEntityRowTitleTemplate(entity),
19057
+ ...titleContext.fallbackTemplates,
19058
+ ].filter((template) => !!template?.trim());
19059
+ for (const template of templates) {
19060
+ const formatted = formatService.format(normalizeEntityDisplayTemplate(template), 'string', rowData);
19061
+ const fromTemplate = resolveEntityRowDisplayText(formatted, activeLang);
19062
+ if (fromTemplate) {
19063
+ return fromTemplate;
19030
19064
  }
19031
- return updatedFiles;
19032
19065
  }
19033
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
19034
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, providedIn: 'root' }); }
19066
+ return '';
19035
19067
  }
19036
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, decorators: [{
19037
- type: Injectable,
19038
- args: [{
19039
- providedIn: 'root',
19040
- }]
19041
- }] });
19068
+ //#endregion
19042
19069
 
19070
+ //#region ---- Imports ----
19071
+ //#endregion
19072
+ //#region ---- Component ----
19043
19073
  class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19044
19074
  //#endregion
19045
19075
  //#region ---- Lifecycle ----
19046
19076
  constructor() {
19047
19077
  super();
19078
+ //#region ---- Services & Dependencies ----
19079
+ this.queryExecutor = inject(AXPQueryExecutor);
19080
+ this.entityResolver = inject(AXPEntityDefinitionRegistryService);
19081
+ this.formatService = inject(AXFormatService);
19082
+ this.translation = inject(AXTranslationService);
19083
+ //#endregion
19084
+ //#region ---- View Children ----
19085
+ this.fileListTrigger = viewChild('fileListTrigger', ...(ngDevMode ? [{ debugName: "fileListTrigger" }] : /* istanbul ignore next */ []));
19086
+ this.fileListPopover = viewChild('fileListPopover', ...(ngDevMode ? [{ debugName: "fileListPopover" }] : /* istanbul ignore next */ []));
19087
+ //#endregion
19048
19088
  //#region ---- State ----
19049
19089
  this.fileCount = signal(0, ...(ngDevMode ? [{ debugName: "fileCount" }] : /* istanbul ignore next */ []));
19090
+ this.popoverFiles = signal([], ...(ngDevMode ? [{ debugName: "popoverFiles" }] : /* istanbul ignore next */ []));
19091
+ this.loadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "loadStatus" }] : /* istanbul ignore next */ []));
19092
+ this.loadError = signal(null, ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
19093
+ this.isPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPopoverOpen" }] : /* istanbul ignore next */ []));
19094
+ this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : /* istanbul ignore next */ []));
19050
19095
  this.loadRequestId = 0;
19096
+ this.popoverLoadRequestId = 0;
19051
19097
  //#endregion
19052
- //#region ---- Services & Dependencies ----
19053
- this.fileService = inject(AXPFileUploaderWidgetService);
19054
- this.queryExecutor = inject(AXPQueryExecutor);
19098
+ //#region ---- Computed ----
19099
+ this.entityScope = computed(() => resolveFileUploaderEntityScope(this.options ?? {}, this.rowData, this.path), ...(ngDevMode ? [{ debugName: "entityScope" }] : /* istanbul ignore next */ []));
19100
+ this.popoverTitle = computed(() => {
19101
+ const row = this.rowData;
19102
+ const lang = this.translation.getActiveLang();
19103
+ const titleRaw = get(row, 'title');
19104
+ if (titleRaw != null &&
19105
+ typeof titleRaw === 'object' &&
19106
+ !Array.isArray(titleRaw) &&
19107
+ this.translation.isValidMultiLanguageObject(titleRaw)) {
19108
+ return titleRaw;
19109
+ }
19110
+ const resolved = formatEntityRowDisplayTitle(this.entityDef(), row, this.formatService, lang);
19111
+ if (resolved) {
19112
+ return resolved;
19113
+ }
19114
+ const fallback = this.entityDef()?.formats?.individual;
19115
+ if (fallback != null && fallback !== '') {
19116
+ return resolveEntityRowDisplayText(fallback, lang) || fallback;
19117
+ }
19118
+ return '@document-management:terms.common.file';
19119
+ }, ...(ngDevMode ? [{ debugName: "popoverTitle" }] : /* istanbul ignore next */ []));
19055
19120
  effect(() => {
19056
19121
  const raw = this.rawValue;
19057
19122
  const row = this.rowData;
19058
19123
  const opts = this.options ?? {};
19059
19124
  const path = this.path;
19125
+ const rowId = row?.['id'] != null ? String(row['id']) : undefined;
19126
+ if (rowId !== this.previousRowId) {
19127
+ this.previousRowId = rowId;
19128
+ untracked(() => {
19129
+ this.popoverFiles.set([]);
19130
+ this.loadStatus.set('idle');
19131
+ this.loadError.set(null);
19132
+ this.popoverLoadRequestId++;
19133
+ });
19134
+ }
19060
19135
  const inlineCount = attachmentFieldCount(raw);
19061
19136
  if (inlineCount > 0) {
19062
19137
  untracked(() => this.fileCount.set(inlineCount));
@@ -19069,7 +19144,6 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19069
19144
  }
19070
19145
  const requestId = ++this.loadRequestId;
19071
19146
  untracked(() => {
19072
- console.log('loadFiles for count', entity);
19073
19147
  void this.queryExecutor
19074
19148
  .fetch('FileUploader:LoadFiles', entity)
19075
19149
  .then((result) => {
@@ -19086,51 +19160,102 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19086
19160
  });
19087
19161
  });
19088
19162
  });
19163
+ effect(() => {
19164
+ const scope = this.entityScope();
19165
+ if (!scope?.name?.includes('.')) {
19166
+ this.entityDef.set(null);
19167
+ return;
19168
+ }
19169
+ const [module, name] = scope.name.split('.');
19170
+ void this.entityResolver.resolve(module, name).then((def) => this.entityDef.set(def));
19171
+ });
19089
19172
  }
19090
19173
  //#endregion
19091
19174
  //#region ---- UI Handlers ----
19092
- async openFileList() {
19093
- const entity = resolveFileUploaderEntityScope(this.options ?? {}, this.rowData, this.path);
19094
- await this.fileService.showFileList({
19095
- entity,
19096
- readonly: true,
19097
- });
19175
+ openFileListPopover() {
19176
+ this.openPopoverFromRef(this.fileListTrigger());
19177
+ void this.ensurePopoverFilesLoaded();
19098
19178
  }
19099
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
19100
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileUploaderWidgetColumnComponent, isStandalone: true, selector: "axp-file-uploader-widget-column", inputs: { rawValue: "rawValue", rowData: "rowData" }, usesInheritance: true, ngImport: i0, template: `
19101
- @if (fileCount() > 0) {
19102
- <span
19103
- class="ax-cursor-pointer ax-text-primary ax-underline"
19104
- (click)="openFileList()"
19105
- >
19106
- {{ fileCount() }} {{ '@document-management:file' | translate | async }}
19107
- </span>
19108
- } @else {
19109
- <span class="ax-text-muted">---</span>
19179
+ onPopoverOpenChange(event) {
19180
+ const open = this.coercePopoverOpenEvent(event);
19181
+ this.isPopoverOpen.set(open);
19182
+ if (open) {
19183
+ void this.ensurePopoverFilesLoaded();
19184
+ }
19185
+ }
19186
+ //#endregion
19187
+ //#region ---- Private Methods ----
19188
+ openPopoverFromRef(ref) {
19189
+ const popover = this.fileListPopover();
19190
+ if (popover && ref) {
19191
+ popover.target = ref.nativeElement;
19192
+ popover.open();
19193
+ this.isPopoverOpen.set(true);
19194
+ }
19195
+ }
19196
+ async ensurePopoverFilesLoaded() {
19197
+ if (this.loadStatus() === 'loading') {
19198
+ return;
19199
+ }
19200
+ const inline = normalizeEntityFieldToFileList(this.rawValue);
19201
+ if (inline.length > 0) {
19202
+ this.popoverFiles.set(inline);
19203
+ this.loadStatus.set('ready');
19204
+ this.loadError.set(null);
19205
+ return;
19206
+ }
19207
+ const entity = this.entityScope();
19208
+ if (!entity) {
19209
+ this.popoverFiles.set([]);
19210
+ this.loadStatus.set('ready');
19211
+ return;
19212
+ }
19213
+ if (this.loadStatus() === 'ready' && this.popoverFiles().length > 0) {
19214
+ return;
19215
+ }
19216
+ const requestId = ++this.popoverLoadRequestId;
19217
+ this.loadStatus.set('loading');
19218
+ this.loadError.set(null);
19219
+ try {
19220
+ const result = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
19221
+ if (requestId !== this.popoverLoadRequestId) {
19222
+ return;
19223
+ }
19224
+ this.popoverFiles.set(result?.files ?? []);
19225
+ this.loadStatus.set('ready');
19226
+ }
19227
+ catch {
19228
+ if (requestId !== this.popoverLoadRequestId) {
19229
+ return;
19230
+ }
19231
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
19232
+ this.loadError.set(msg);
19233
+ this.loadStatus.set('error');
19234
+ this.popoverFiles.set([]);
19235
+ }
19236
+ }
19237
+ coercePopoverOpenEvent(event) {
19238
+ if (typeof event === 'boolean') {
19239
+ return event;
19240
+ }
19241
+ if (event && typeof event === 'object') {
19242
+ const o = event;
19243
+ if ('detail' in o) {
19244
+ return Boolean(o['detail']);
19245
+ }
19246
+ if ('open' in o) {
19247
+ return Boolean(o['open']);
19248
+ }
19249
+ }
19250
+ return false;
19110
19251
  }
19111
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19252
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
19253
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileUploaderWidgetColumnComponent, isStandalone: true, selector: "axp-file-uploader-widget-column", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "fileListTrigger", first: true, predicate: ["fileListTrigger"], descendants: true, isSignal: true }, { propertyName: "fileListPopover", first: true, predicate: ["fileListPopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (fileCount() > 0) {\n<span class=\"ax-cursor-pointer ax-text-primary ax-underline\" (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger>\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n</span>\n} @else {\n<span class=\"ax-text-muted\">---</span>\n}\n\n<ax-popover [openOn]=\"'manual'\" #fileListPopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[17.5rem] ax-max-w-[26rem] ax-overflow-hidden\">\n <div class=\"ax-border-b ax-border-surface ax-px-4 ax-py-3\">\n <h3 class=\"ax-m-0 ax-text-sm ax-font-semibold ax-leading-normal ax-text-on-lightest-surface\">\n {{ popoverTitle() | translate | async }}\n </h3>\n </div>\n <div class=\"ax-max-h-64 ax-overflow-y-auto ax-px-3 ax-py-3\" style=\"max-height: max(30dvh, 12rem)\">\n @if (loadStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (loadStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ loadError() }}</div>\n } @else {\n <axp-file-list [files]=\"popoverFiles()\" look=\"links\" linksLayout=\"menu\" [showLabel]=\"false\" [readonly]=\"true\" />\n }\n </div>\n </div>\n</ax-popover>", dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19112
19254
  }
19113
19255
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, decorators: [{
19114
19256
  type: Component,
19115
- args: [{
19116
- selector: 'axp-file-uploader-widget-column',
19117
- template: `
19118
- @if (fileCount() > 0) {
19119
- <span
19120
- class="ax-cursor-pointer ax-text-primary ax-underline"
19121
- (click)="openFileList()"
19122
- >
19123
- {{ fileCount() }} {{ '@document-management:file' | translate | async }}
19124
- </span>
19125
- } @else {
19126
- <span class="ax-text-muted">---</span>
19127
- }
19128
- `,
19129
- changeDetection: ChangeDetectionStrategy.OnPush,
19130
- imports: [AXTranslationModule, AsyncPipe],
19131
- inputs: ['rawValue', 'rowData'],
19132
- }]
19133
- }], ctorParameters: () => [] });
19257
+ args: [{ selector: 'axp-file-uploader-widget-column', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXTranslationModule, AsyncPipe, AXPopoverModule, AXLoadingModule, AXPFileListComponent], inputs: ['rawValue', 'rowData'], template: "@if (fileCount() > 0) {\n<span class=\"ax-cursor-pointer ax-text-primary ax-underline\" (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger>\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n</span>\n} @else {\n<span class=\"ax-text-muted\">---</span>\n}\n\n<ax-popover [openOn]=\"'manual'\" #fileListPopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[17.5rem] ax-max-w-[26rem] ax-overflow-hidden\">\n <div class=\"ax-border-b ax-border-surface ax-px-4 ax-py-3\">\n <h3 class=\"ax-m-0 ax-text-sm ax-font-semibold ax-leading-normal ax-text-on-lightest-surface\">\n {{ popoverTitle() | translate | async }}\n </h3>\n </div>\n <div class=\"ax-max-h-64 ax-overflow-y-auto ax-px-3 ax-py-3\" style=\"max-height: max(30dvh, 12rem)\">\n @if (loadStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (loadStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ loadError() }}</div>\n } @else {\n <axp-file-list [files]=\"popoverFiles()\" look=\"links\" linksLayout=\"menu\" [showLabel]=\"false\" [readonly]=\"true\" />\n }\n </div>\n </div>\n</ax-popover>" }]
19258
+ }], ctorParameters: () => [], propDecorators: { fileListTrigger: [{ type: i0.ViewChild, args: ['fileListTrigger', { isSignal: true }] }], fileListPopover: [{ type: i0.ViewChild, args: ['fileListPopover', { isSignal: true }] }] } });
19134
19259
 
19135
19260
  var fileUploaderWidgetColumn_component = /*#__PURE__*/Object.freeze({
19136
19261
  __proto__: null,
@@ -19341,6 +19466,7 @@ class AXPFileUploaderWidgetEditComponent extends AXPValueWidgetComponent {
19341
19466
  }
19342
19467
  this.isDirtySignal.set(dirty);
19343
19468
  this.dirtyChangeSubject.next(dirty);
19469
+ this.layoutService.notifyWidgetDirtyChanged();
19344
19470
  }
19345
19471
  //#endregion
19346
19472
  async loadActions() {
@@ -19574,7 +19700,7 @@ class AXPFileUploaderWidgetEditComponent extends AXPValueWidgetComponent {
19574
19700
  (onRename)="handleFileRename($event)"
19575
19701
  ></axp-file-list>
19576
19702
  </div>
19577
- `, isInline: true, styles: [":host{border-color:rgba(var(--ax-comp-editor-border-color))}:host.axp-file-uploader-widget-edit--borderless{border:none!important}.__drag-over{background-color:rgba(var(--ax-sys-color-primary-50),.1);border:2px dashed rgb(var(--ax-sys-color-primary-500));border-radius:.375rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "component", type: i1.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "directive", type: AXUploaderZoneDirective, selector: "[axUploaderZone]", inputs: ["multiple", "accept", "overlayTemplate", "disableBrowse", "disableDragDrop"], outputs: ["fileChange", "onChanged", "dragEnter", "dragLeave", "dragOver", "onFileUploadComplete", "onFilesUploadComplete"] }, { 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: AXLoadingModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3$3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i6.AXTranslatorDirective, selector: "[translate]" }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19703
+ `, isInline: true, styles: [":host{border-color:rgba(var(--ax-comp-editor-border-color))}:host.axp-file-uploader-widget-edit--borderless{border:none!important}.__drag-over{background-color:rgba(var(--ax-sys-color-primary-50),.1);border:2px dashed rgb(var(--ax-sys-color-primary-500));border-radius:.375rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "component", type: i1.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "directive", type: AXUploaderZoneDirective, selector: "[axUploaderZone]", inputs: ["multiple", "accept", "fileType", "overlayTemplate", "disableBrowse", "disableDragDrop"], outputs: ["fileChange", "onChanged", "dragEnter", "dragLeave", "dragOver", "onFileUploadComplete", "onFilesUploadComplete"] }, { 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: AXLoadingModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3$3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i6.AXTranslatorDirective, selector: "[translate]" }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19578
19704
  }
19579
19705
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetEditComponent, decorators: [{
19580
19706
  type: Component,
@@ -19666,7 +19792,7 @@ class AXPFileUploaderWidgetViewComponent extends AXPValueWidgetComponent {
19666
19792
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
19667
19793
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPFileUploaderWidgetViewComponent, isStandalone: true, selector: "axp-file-uploader-widget-view", host: { properties: { "class": "this.__class" } }, usesInheritance: true, ngImport: i0, template: `
19668
19794
  <axp-file-list [files]="files()" [readonly]="true"></axp-file-list>
19669
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXUploaderModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19795
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXUploaderModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19670
19796
  }
19671
19797
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetViewComponent, decorators: [{
19672
19798
  type: Component,
@@ -19773,6 +19899,78 @@ const AXPFileUploaderWidget = {
19773
19899
  },
19774
19900
  };
19775
19901
 
19902
+ class AXPFileUploaderWidgetService {
19903
+ constructor() {
19904
+ //#region ---- Services & Dependencies ----
19905
+ this.popupService = inject(AXPopupService);
19906
+ this.translate = inject(AXTranslationService);
19907
+ this.commandExecutor = inject(AXPCommandExecutor);
19908
+ this.queryExecutor = inject(AXPQueryExecutor);
19909
+ }
19910
+ //#endregion
19911
+ //#region ---- Public API ----
19912
+ async showFileList(options) {
19913
+ const entity = options?.entity;
19914
+ let files = options?.files;
19915
+ if (entity) {
19916
+ const filesResult = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
19917
+ if (!filesResult) {
19918
+ return undefined;
19919
+ }
19920
+ files = filesResult.files ?? [];
19921
+ }
19922
+ const resolved = {
19923
+ files: files ?? [],
19924
+ readonly: options?.readonly ?? false,
19925
+ multiple: options?.multiple ?? false,
19926
+ accept: options?.accept ?? '*',
19927
+ fileEditable: options?.fileEditable ?? true,
19928
+ maxFileSize: options?.maxFileSize ?? 1024 * 1024 * 10,
19929
+ showAddItemButton: options?.showAddItemButton ?? true,
19930
+ plugins: options?.plugins ?? [],
19931
+ editDialog: options?.editDialog,
19932
+ };
19933
+ const component = await import('./acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs').then((m) => m.AXPFileListPopupComponent);
19934
+ const result = await this.popupService.open(component, {
19935
+ title: await this.translate.translateAsync('@document-management:terms.common.file'),
19936
+ data: {
19937
+ files: signal(resolved.files),
19938
+ isReadonly: signal(resolved.readonly),
19939
+ multiple: signal(resolved.multiple),
19940
+ accept: signal(resolved.accept),
19941
+ maxFileSize: signal(resolved.maxFileSize),
19942
+ fileEditable: signal(resolved.fileEditable),
19943
+ showAddItemButton: signal(resolved.showAddItemButton),
19944
+ plugins: signal(resolved.plugins),
19945
+ editDialog: signal(resolved.editDialog),
19946
+ },
19947
+ });
19948
+ const updatedFiles = result?.data?.data?.files;
19949
+ if (!updatedFiles) {
19950
+ return undefined;
19951
+ }
19952
+ if (!entity) {
19953
+ return updatedFiles;
19954
+ }
19955
+ const saveResult = await this.commandExecutor.execute('FileUploader:SaveFiles', {
19956
+ ...entity,
19957
+ files: updatedFiles,
19958
+ });
19959
+ if (!saveResult?.success) {
19960
+ return undefined;
19961
+ }
19962
+ return updatedFiles;
19963
+ }
19964
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
19965
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, providedIn: 'root' }); }
19966
+ }
19967
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, decorators: [{
19968
+ type: Injectable,
19969
+ args: [{
19970
+ providedIn: 'root',
19971
+ }]
19972
+ }] });
19973
+
19776
19974
  var index = /*#__PURE__*/Object.freeze({
19777
19975
  __proto__: null,
19778
19976
  AXPEditFileUploaderCommand: AXPEditFileUploaderCommand,
@@ -21211,7 +21409,7 @@ class AXPLayoutAdapterBuilder {
21211
21409
  load: this.createLoadFunction(),
21212
21410
  pages: this.adapter.pages || [],
21213
21411
  exitUrl: this.adapter.exitUrl,
21214
- //getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
21412
+ getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
21215
21413
  getPageStatus: (context, currentPage) => this.badgeStatusService.getPageStatus(name, context, currentPage),
21216
21414
  };
21217
21415
  }
@@ -21556,7 +21754,7 @@ class AXPPageComponentConverter {
21556
21754
  return empty;
21557
21755
  }
21558
21756
  const tempInjector = Injector.create({
21559
- providers: [{ provide: ComponentType, useClass: ComponentType }],
21757
+ providers: [AXPContextStore, { provide: ComponentType, useClass: ComponentType }],
21560
21758
  parent: this.injector,
21561
21759
  });
21562
21760
  const componentInstance = tempInjector.get(ComponentType);
@@ -21754,6 +21952,20 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
21754
21952
  if (e.name === 'update-entity') {
21755
21953
  const fn = entityDef?.commands?.update?.execute;
21756
21954
  const result = await fn(context);
21955
+ if (result && typeof result === 'object' && 'success' in result) {
21956
+ const cmd = result;
21957
+ if (!cmd.success) {
21958
+ const errorText = commandMessageTextForError(cmd.message?.text) || 'Failed to update entity';
21959
+ return {
21960
+ success: false,
21961
+ message: cmd.message ?? { text: errorText },
21962
+ };
21963
+ }
21964
+ return {
21965
+ success: true,
21966
+ data: cmd.data ?? result,
21967
+ };
21968
+ }
21757
21969
  return {
21758
21970
  success: true,
21759
21971
  data: result,
@@ -21807,12 +22019,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
21807
22019
  }
21808
22020
  }
21809
22021
  catch (error) {
21810
- return {
21811
- success: false,
21812
- message: {
21813
- text: error,
21814
- },
21815
- };
22022
+ return mapEntityStorageErrorToCommandResult(error);
21816
22023
  }
21817
22024
  };
21818
22025
  }
@@ -21890,6 +22097,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
21890
22097
  title: context.rootTitle ?? '',
21891
22098
  label: relatedEntity.title,
21892
22099
  icon: relatedEntity.icon || entityDef.icon,
22100
+ /** List-only page: scoped filters are system state, not user edits. */
22101
+ isReadonly: true,
21893
22102
  actions: this.mergeActions(entityDef, actions)
21894
22103
  ?.filter((a) => a.priority === 'primary' && !a.isChild)
21895
22104
  ?.map((a) => {
@@ -22572,6 +22781,7 @@ class AXPMainEntityContentBuilder {
22572
22781
  icon: 'fa-light fa-rotate-left',
22573
22782
  color: 'default',
22574
22783
  visible: '{{context.isDirty()}}',
22784
+ shortcuts: ['escape'],
22575
22785
  command: {
22576
22786
  name: 'discard',
22577
22787
  },
@@ -22581,6 +22791,7 @@ class AXPMainEntityContentBuilder {
22581
22791
  icon: 'fa-light fa-floppy-disk',
22582
22792
  color: 'primary',
22583
22793
  visible: '{{context.isDirty()}}',
22794
+ shortcuts: ['ctrl+s'],
22584
22795
  command: {
22585
22796
  name: 'update-entity',
22586
22797
  },
@@ -22604,6 +22815,20 @@ class AXPMainEntityContentBuilder {
22604
22815
  if (e.name == 'update-entity') {
22605
22816
  const fn = entity?.commands?.update?.execute;
22606
22817
  const result = await fn(context);
22818
+ if (result && typeof result === 'object' && 'success' in result) {
22819
+ const cmd = result;
22820
+ if (!cmd.success) {
22821
+ const errorText = commandMessageTextForError(cmd.message?.text) || 'Failed to update entity';
22822
+ return {
22823
+ success: false,
22824
+ message: cmd.message ?? { text: errorText },
22825
+ };
22826
+ }
22827
+ return {
22828
+ success: true,
22829
+ data: cmd.data ?? result,
22830
+ };
22831
+ }
22607
22832
  return {
22608
22833
  success: true,
22609
22834
  data: result,
@@ -22657,12 +22882,7 @@ class AXPMainEntityContentBuilder {
22657
22882
  }
22658
22883
  }
22659
22884
  catch (error) {
22660
- return {
22661
- success: false,
22662
- message: {
22663
- text: error,
22664
- },
22665
- };
22885
+ return mapEntityStorageErrorToCommandResult(error);
22666
22886
  }
22667
22887
  },
22668
22888
  tabs: [...tabDetailTabs, ...tabListTabs, ...nestedTabListTabs],
@@ -22865,6 +23085,7 @@ class AXPMainEntityContentBuilder {
22865
23085
  icon: action.icon,
22866
23086
  color: action.color,
22867
23087
  disabled: disabled || false,
23088
+ shortcuts: action.shortcuts,
22868
23089
  zone: 'header',
22869
23090
  priority: action.priority,
22870
23091
  scope: action.scope === AXPEntityCommandScope.Individual ? AXPEntityCommandScope.TypeLevel : action.scope,
@@ -22927,17 +23148,25 @@ class AXPLayoutAdapterFactory {
22927
23148
  async createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies) {
22928
23149
  const entity = await entityResolver.resolve(moduleName, entityName);
22929
23150
  if (!entity) {
22930
- throw new Error(`Entity ${moduleName}.${entityName} not found`);
23151
+ throw new AXPNotFoundError(`Entity ${moduleName}.${entityName} not found`);
22931
23152
  }
22932
23153
  const rootContext = await this.loadRootContext(entity, id);
22933
- // Evaluate hidden expressions for related entities once, reuse across building and composing
22934
- const evaluatedRelatedEntities = await this.evaluateRelatedEntitiesHidden(entity?.relatedEntities ?? [], rootContext, dependencies);
22935
- // Build main and related pages using evaluated related entities
22936
- const entityWithEvaluatedRelated = { ...entity, relatedEntities: evaluatedRelatedEntities };
22937
- const mainPage = await this.buildMainPage(entityWithEvaluatedRelated, rootContext, id, dependencies);
22938
- const relatedPages = await this.buildRelatedPages(entityWithEvaluatedRelated, rootContext, dependencies);
23154
+ if (axpIsEntityRecordNotFound(rootContext)) {
23155
+ throw new AXPNotFoundError(`Entity record ${moduleName}.${entityName}#${id} not found`);
23156
+ }
23157
+ // Evaluate hidden expressions for related entities and component pages once, reuse across building and composing
23158
+ const evaluatedRelatedEntities = await this.evaluateHiddenFlags(entity?.relatedEntities ?? [], rootContext, dependencies);
23159
+ const evaluatedPages = await this.evaluateHiddenFlags(entity?.pages ?? [], rootContext, dependencies);
23160
+ // Build main and related pages using evaluated related entities and pages
23161
+ const entityWithEvaluatedLayout = {
23162
+ ...entity,
23163
+ relatedEntities: evaluatedRelatedEntities,
23164
+ pages: evaluatedPages,
23165
+ };
23166
+ const mainPage = await this.buildMainPage(entityWithEvaluatedLayout, rootContext, id, dependencies);
23167
+ const relatedPages = await this.buildRelatedPages(entityWithEvaluatedLayout, rootContext, dependencies);
22939
23168
  // Compose ordered pages around the primary page
22940
- const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedRelated);
23169
+ const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedLayout);
22941
23170
  const applicationName = dependencies.session.application?.name ?? 'default';
22942
23171
  const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
22943
23172
  return this.layoutAdapterBuilder
@@ -22956,15 +23185,15 @@ class AXPLayoutAdapterFactory {
22956
23185
  return this.mainEntityContentBuilder.build(entity, rootContext, { ...dependencies, reloadRootContext: () => this.loadRootContext(entity, id) }, await this.getRootTitle(entity, rootContext, dependencies));
22957
23186
  }
22958
23187
  /**
22959
- * Evaluates the 'hidden' expression for each related entity using the expression evaluator.
22960
- * Returns a new array of related entities with the evaluated hidden values.
23188
+ * Evaluates the 'hidden' expression for layout items using the expression evaluator.
23189
+ * Returns a new array with evaluated hidden values.
22961
23190
  */
22962
- async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
22963
- if (!relatedEntities?.length || !dependencies?.expressionEvaluator) {
22964
- return relatedEntities ?? [];
23191
+ async evaluateHiddenFlags(items, rootContext, dependencies) {
23192
+ if (!items?.length || !dependencies?.expressionEvaluator) {
23193
+ return items ?? [];
22965
23194
  }
22966
- return Promise.all(relatedEntities.map(async (re) => {
22967
- let hidden = re.hidden;
23195
+ return Promise.all(items.map(async (item) => {
23196
+ let hidden = item.hidden;
22968
23197
  if (hidden && typeof hidden === 'string') {
22969
23198
  try {
22970
23199
  const scope = {
@@ -22979,9 +23208,12 @@ class AXPLayoutAdapterFactory {
22979
23208
  // Keep original hidden value if evaluation fails
22980
23209
  }
22981
23210
  }
22982
- return { ...re, hidden };
23211
+ return { ...item, hidden };
22983
23212
  }));
22984
23213
  }
23214
+ async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
23215
+ return this.evaluateHiddenFlags(relatedEntities, rootContext, dependencies);
23216
+ }
22985
23217
  async buildRelatedPages(entity, rootContext, dependencies) {
22986
23218
  const pages = [];
22987
23219
  const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
@@ -23146,7 +23378,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
23146
23378
  }]
23147
23379
  }], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4$3.AXPFilterOperatorMiddlewareService }] });
23148
23380
 
23149
- 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)) => {
23381
+ 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)) => {
23150
23382
  const moduleName = route.parent?.paramMap.get('module');
23151
23383
  const entityName = route.paramMap.get('entity');
23152
23384
  const id = route.paramMap.get('id');
@@ -23158,8 +23390,15 @@ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver =
23158
23390
  workflowService,
23159
23391
  commandService,
23160
23392
  };
23161
- const adapter = await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
23162
- return adapter;
23393
+ try {
23394
+ return await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
23395
+ }
23396
+ catch (error) {
23397
+ if (error instanceof AXPNotFoundError) {
23398
+ return axpRedirectToNotFound(router);
23399
+ }
23400
+ throw error;
23401
+ }
23163
23402
  };
23164
23403
 
23165
23404
  class AXPEntityPreloadFilterGuard {
@@ -23218,32 +23457,33 @@ const eventDispatchMiddleware = {
23218
23457
  const dispatcher = inject(AXPEntityEventDispatcherService);
23219
23458
  const workflowEventService = inject(AXPWorkflowEventService);
23220
23459
  await next();
23221
- if (ctx.op === 'create') {
23222
- const createdData = ctx.result ? { ...ctx.data, id: ctx.result } : ctx.data;
23223
- await dispatcher.dispatchInserted(ctx.entityName, { refType: ctx.entityName, data: createdData });
23224
- // Dispatch workflow refresh event to trigger entity list refresh
23225
- workflowEventService.dispatch(AXPRefreshEvent({
23226
- entity: ctx.entityName,
23227
- }));
23228
- }
23229
- else if (ctx.op === 'update') {
23230
- await dispatcher.dispatchUpdated(ctx.entityName, {
23231
- refType: ctx.entityName,
23232
- data: ctx.result,
23233
- changes: ctx.locals.get('changes'),
23234
- });
23235
- // Dispatch workflow refresh event to trigger entity list refresh
23236
- workflowEventService.dispatch(AXPRefreshEvent({
23237
- entity: ctx.entityName,
23238
- }));
23460
+ try {
23461
+ if (ctx.op === 'create') {
23462
+ const createdData = ctx.result ? { ...ctx.data, id: ctx.result } : ctx.data;
23463
+ await dispatcher.dispatchInserted(ctx.entityName, { refType: ctx.entityName, data: createdData });
23464
+ workflowEventService.dispatch(AXPRefreshEvent({
23465
+ entity: ctx.entityName,
23466
+ }));
23467
+ }
23468
+ else if (ctx.op === 'update') {
23469
+ await dispatcher.dispatchUpdated(ctx.entityName, {
23470
+ refType: ctx.entityName,
23471
+ data: ctx.result,
23472
+ changes: ctx.locals.get('changes'),
23473
+ });
23474
+ workflowEventService.dispatch(AXPRefreshEvent({
23475
+ entity: ctx.entityName,
23476
+ }));
23477
+ }
23478
+ else if (ctx.op === 'delete') {
23479
+ await dispatcher.dispatchDeleted(ctx.entityName, { refType: ctx.entityName, data: ctx.result ?? ctx.previous });
23480
+ workflowEventService.dispatch(AXPRefreshEvent({
23481
+ entity: ctx.entityName,
23482
+ }));
23483
+ }
23239
23484
  }
23240
- else if (ctx.op === 'delete') {
23241
- // For delete, prefer previous entity if available
23242
- await dispatcher.dispatchDeleted(ctx.entityName, { refType: ctx.entityName, data: ctx.result ?? ctx.previous });
23243
- // Dispatch workflow refresh event to trigger entity list refresh
23244
- workflowEventService.dispatch(AXPRefreshEvent({
23245
- entity: ctx.entityName,
23246
- }));
23485
+ catch {
23486
+ // Persist already succeeded; event dispatch must not block the write result.
23247
23487
  }
23248
23488
  },
23249
23489
  };
@@ -23904,8 +24144,12 @@ const AXPCrudModifier = {
23904
24144
  if (!command?.update) {
23905
24145
  command.update = {
23906
24146
  execute: async (data) => {
23907
- console.log('update', ctx.module.get() + '.' + ctx.name.get(), data);
23908
- return await dataService.updateOne(data.id, data);
24147
+ const entityKey = ctx.module.get() + '.' + ctx.name.get();
24148
+ const { id, ...patch } = data ?? {};
24149
+ if (id == null || id === '') {
24150
+ throw new Error(`Update requires entity id (${entityKey})`);
24151
+ }
24152
+ return await dataService.updateOne(id, patch);
23909
24153
  },
23910
24154
  };
23911
24155
  }
@@ -23928,7 +24172,7 @@ const AXPCrudModifier = {
23928
24172
  queries.list = {
23929
24173
  execute: async (e) => {
23930
24174
  const res = await dataService.query(e);
23931
- console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
24175
+ // console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
23932
24176
  return res;
23933
24177
  },
23934
24178
  type: AXPEntityQueryType.List,
@@ -23971,6 +24215,8 @@ class AXPEntitySearchDefinitionProvider {
23971
24215
  this.entityRegister.getAll().forEach((entity) => {
23972
24216
  const manifest = this.manifestRegistry.get(entity.module);
23973
24217
  const i18nScope = manifest?.i18n ?? moduleNameToI18nScope(entity.module);
24218
+ const searchTitleTemplate = entity.formats.searchResult?.title;
24219
+ const titleContext = buildEntitySearchTitleContext(entity, searchTitleTemplate);
23974
24220
  context.addDefinition(`Module.${entity.module}.${entity.name}`, `@${i18nScope}:module.title`, `Module.${entity.module}`, entity.icon ?? 'fa-solid fa-objects-column', 4, {
23975
24221
  actions: [
23976
24222
  {
@@ -23980,43 +24226,17 @@ class AXPEntitySearchDefinitionProvider {
23980
24226
  },
23981
24227
  ],
23982
24228
  format: {
23983
- title: entity.formats.searchResult?.title,
24229
+ title: searchTitleTemplate,
23984
24230
  description: (entity.formats.searchResult?.description) ?? `${entity.module} / ${entity.title}`,
23985
24231
  id: '{{data.id}}',
24232
+ titleFallbackTemplates: titleContext.fallbackTemplates,
24233
+ titleFallbackFields: titleContext.fallbackFields,
23986
24234
  },
23987
24235
  });
23988
24236
  });
23989
24237
  }
23990
24238
  }
23991
24239
 
23992
- /**
23993
- * Error type that can be thrown by middlewares to abort the chain with a structured payload.
23994
- */
23995
- class AXPMiddlewareAbortError extends Error {
23996
- constructor(message, options = {}) {
23997
- super(message);
23998
- this.message = message;
23999
- this.options = options;
24000
- this.name = 'AXPMiddlewareAbortError';
24001
- }
24002
- toResponse() {
24003
- return {
24004
- success: false,
24005
- error: {
24006
- code: this.options.code,
24007
- message: this.message,
24008
- status: this.options.status,
24009
- details: this.options.details,
24010
- },
24011
- };
24012
- }
24013
- }
24014
- /** Type guard for AXPMiddlewareAbortError */
24015
- function isAXPMiddlewareAbortError(error) {
24016
- return error instanceof AXPMiddlewareAbortError;
24017
- }
24018
- //#endregion
24019
-
24020
24240
  const AXP_ENTITY_STORAGE_BACKEND = new InjectionToken('AXP_ENTITY_STORAGE_BACKEND');
24021
24241
  const AXP_ENTITY_STORAGE_MIDDLEWARE = new InjectionToken('AXP_ENTITY_STORAGE_MIDDLEWARE');
24022
24242
 
@@ -24694,7 +24914,7 @@ function routesFacory() {
24694
24914
  loadComponent: () => {
24695
24915
  return config.viewers.root();
24696
24916
  },
24697
- canActivate: [AXPAuthGuard],
24917
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
24698
24918
  children: [
24699
24919
  {
24700
24920
  path: ':module',
@@ -24732,6 +24952,7 @@ function routesFacory() {
24732
24952
  loadComponent: () => {
24733
24953
  return config.viewers.master.details();
24734
24954
  },
24955
+ canDeactivate: [axpDetailsViewCanDeactivateGuard],
24735
24956
  runGuardsAndResolvers: (from, to) => {
24736
24957
  const entityChanged = from.params['module'] !== to.params['module'] || from.params['entity'] !== to.params['entity'];
24737
24958
  const idChanged = from.params['id'] !== to.params['id'];
@@ -24750,8 +24971,16 @@ function routesFacory() {
24750
24971
  redirectTo: ':entity/list',
24751
24972
  pathMatch: 'full',
24752
24973
  },
24974
+ {
24975
+ path: '**',
24976
+ redirectTo: AXP_NOT_FOUND_ROUTE,
24977
+ },
24753
24978
  ],
24754
24979
  },
24980
+ {
24981
+ path: '**',
24982
+ redirectTo: AXP_NOT_FOUND_ROUTE,
24983
+ },
24755
24984
  ],
24756
24985
  },
24757
24986
  ];
@@ -25305,5 +25534,5 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
25305
25534
  * Generated bundle index. Do not edit.
25306
25535
  */
25307
25536
 
25308
- 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, 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, mapLegacyEntityDataSelectorOptions, mergeForeignKeyFieldIntoCreateActions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, normalizeLookupDisplayTemplate, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
25537
+ 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, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, mergeForeignKeyFieldIntoCreateActions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
25309
25538
  //# sourceMappingURL=acorex-platform-layout-entity.mjs.map