@acorex/platform 21.0.0-next.89 → 21.0.0-next.91

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 (58) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +1 -1
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/acorex-platform-common-common-settings.provider-DilV4dql.mjs.map +1 -1
  4. package/fesm2022/acorex-platform-common.mjs +3 -10
  5. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-contracts.mjs +1737 -27
  7. package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-core.mjs +28 -1712
  9. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-components.mjs +3 -3
  13. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-entity-contracts.mjs +27 -1
  17. package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-entity.mjs +355 -122
  20. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-views.mjs +77 -16
  22. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  23. package/fesm2022/acorex-platform-layout-widget-core.mjs +89 -11
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  25. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -1
  26. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -1
  27. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-CZc2zFlg.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-BvFykMTH.mjs} +2 -2
  28. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-BvFykMTH.mjs.map +1 -0
  29. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-XWRUVoL4.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs} +2 -2
  30. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-XWRUVoL4.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs.map} +1 -1
  31. package/fesm2022/acorex-platform-layout-widgets.mjs +5 -7
  32. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  33. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  34. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-CWLfNqV0.mjs.map +1 -1
  35. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-C7cT82K2.mjs.map +1 -1
  36. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -1
  37. package/fesm2022/acorex-platform-themes-default.mjs +15 -5
  38. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  39. package/fesm2022/{acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs → acorex-platform-themes-shared-settings.provider-DxtfyNGS.mjs} +2 -2
  40. package/fesm2022/acorex-platform-themes-shared-settings.provider-DxtfyNGS.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-themes-shared.mjs +2 -2
  42. package/fesm2022/acorex-platform-workflow.mjs +2 -2
  43. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/types/acorex-platform-common.d.ts +3 -5
  46. package/types/acorex-platform-contracts.d.ts +863 -7
  47. package/types/acorex-platform-core.d.ts +65 -922
  48. package/types/acorex-platform-layout-builder.d.ts +3 -3
  49. package/types/acorex-platform-layout-components.d.ts +6 -6
  50. package/types/acorex-platform-layout-designer.d.ts +4 -5
  51. package/types/acorex-platform-layout-entity-contracts.d.ts +94 -3
  52. package/types/acorex-platform-layout-entity.d.ts +72 -103
  53. package/types/acorex-platform-layout-views.d.ts +19 -12
  54. package/types/acorex-platform-layout-widget-core.d.ts +89 -77
  55. package/types/acorex-platform-layout-widgets.d.ts +9 -8
  56. package/types/acorex-platform-themes-default.d.ts +4 -1
  57. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-CZc2zFlg.mjs.map +0 -1
  58. package/fesm2022/acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs.map +0 -1
@@ -1,21 +1,20 @@
1
- import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, AXPEntityDataSourceKeys, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION, buildEntitySearchTitleContext } from '@acorex/platform/layout/entity-contracts';
1
+ import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, AXPEntityCommandScope, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, AXPEntityDataSourceKeys, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION, resolveLookupWidgetCreateOptions, AXPEntityQueryType, buildEntitySearchTitleContext } from '@acorex/platform/layout/entity-contracts';
2
2
  export * from '@acorex/platform/layout/entity-contracts';
3
3
  export { collectNestedCreateHiddenProperties, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, mergeForeignKeyFieldIntoCreateActions } from '@acorex/platform/layout/entity-contracts';
4
4
  import { AXToastService } from '@acorex/components/toast';
5
5
  import * as i3$1 from '@acorex/core/translation';
6
6
  import { AXTranslationService, resolveMultiLanguageString, AXTranslationModule, translateSync } from '@acorex/core/translation';
7
7
  import * as i4$2 from '@acorex/platform/common';
8
- import { AXPNotFoundError, AXPEntityCommandScope, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, resolveEntityCreateListBehavior, AXPRefreshEvent, resolveEntityModifyPopupTitle, resolveSearchableText, evaluateEntityDisplayTemplate, AXPFilterOperatorMiddlewareService, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPReloadEvent, axpRedirectToNotFound, AXP_ENTITY_DELETED_WORKFLOW_EVENT_TYPE, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, resolveEntityRowDisplayTitle, AXPFileActionsService, axpIsEntityRecordNotFound, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
8
+ import { AXPNotFoundError, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, resolveEntityCreateListBehavior, AXPRefreshEvent, resolveEntityModifyPopupTitle, resolveSearchableText, evaluateEntityDisplayTemplate, AXPFilterOperatorMiddlewareService, AXPReloadEvent, axpRedirectToNotFound, AXP_ENTITY_DELETED_WORKFLOW_EVENT_TYPE, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, resolveEntityRowDisplayTitle, AXPFileActionsService, axpIsEntityRecordNotFound, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPWorkflowNavigateAction, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
9
9
  import * as i1$3 from '@acorex/platform/workflow';
10
10
  import { AXPWorkflowEventService, AXPWorkflowService, ofType, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowModule } from '@acorex/platform/workflow';
11
11
  import * as i0 from '@angular/core';
12
12
  import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, ChangeDetectorRef, input, signal, computed, effect, ChangeDetectionStrategy, Component, ElementRef, NgModule, viewChild, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, EnvironmentInjector, makeEnvironmentProviders } from '@angular/core';
13
- import { cloneDeep, merge, get, castArray, set, orderBy, isNil, isEmpty, isEqual as isEqual$1, isObjectLike, isArray, has, isString } from 'lodash-es';
13
+ import { cloneDeep, merge, get, castArray, set, orderBy, isNil, isEmpty, isEqual, isObjectLike, isArray, has, isString } from 'lodash-es';
14
14
  import { Subject, filter, takeUntil } from 'rxjs';
15
+ import { AXPWidgetsCatalog, resolveActionLook, AXPPlatformScope, extractValue, setSmart, getChangedPaths, isSelectionValueEqual, AXPDataGenerator, AXPSystemActionType } from '@acorex/platform/contracts';
15
16
  import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
16
- import { AXPWidgetsCatalog, AXPPlatformScope, AXPDataGenerator, AXPSystemActionType } from '@acorex/platform/contracts';
17
- import { AXPDeviceService, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXHighlightService, extractValue, setSmart, getChangedPaths, AXPContextStore, isSelectionValueEqual, AXPDebugService, FORM_DIRTY_TRACE_NS, objectKeyValueTransforms, setupPopupFooterKeyboardShortcuts, AXPHookService, AXPComponentSlotModule, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
18
- import { transform, isEqual } from 'lodash';
17
+ import { AXPDeviceService, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPBroadcastEventService, applyFilterArray, applySortArray, AXPDistributedEventListenerService, AXHighlightService, AXPDebugService, AXPContextStore, FORM_DIRTY_TRACE_NS, objectKeyValueTransforms, setupPopupFooterKeyboardShortcuts, AXPHookService, AXPComponentSlotModule, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
19
18
  import { AXPSessionService, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
20
19
  import { Router, NavigationStart, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
21
20
  import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, AXPCommandExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
@@ -25,7 +24,7 @@ import { AXButtonModule } from '@acorex/components/button';
25
24
  import * as i3 from '@acorex/components/skeleton';
26
25
  import { AXSkeletonModule } from '@acorex/components/skeleton';
27
26
  import * as i2 from '@acorex/platform/layout/widget-core';
28
- import { 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';
27
+ import { AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, AXPBaseWidgetComponent, AXPWidgetStatus, WIDGET_CASCADE_TRACE_NS, 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';
29
28
  import * as i5 from '@angular/common';
30
29
  import { CommonModule, DOCUMENT, AsyncPipe, NgClass } from '@angular/common';
31
30
  import { AXOverlayService } from '@acorex/cdk/overlay';
@@ -79,6 +78,7 @@ import { AXUploaderZoneDirective } from '@acorex/cdk/uploader';
79
78
  import { AXUploaderModule } from '@acorex/components/uploader';
80
79
  import * as i4$1 from '@acorex/components/collapse';
81
80
  import { AXCollapseModule } from '@acorex/components/collapse';
81
+ import { transform, isEqual as isEqual$1 } from 'lodash';
82
82
 
83
83
  function ensureListActions(ctx) {
84
84
  ctx.interfaces.update((i) => {
@@ -100,6 +100,10 @@ function actionExists(actions, commandName, name) {
100
100
  return cmd === commandName;
101
101
  });
102
102
  }
103
+ /** Layout order for the shared appearance section (after entity-defined groups). */
104
+ const AXP_APPEARANCE_SECTION_ORDER = 900;
105
+ /** Layout order for the shared classification section (after appearance). */
106
+ const AXP_CLASSIFICATION_SECTION_ORDER = 910;
103
107
  /**
104
108
  * Clones `sections` and `properties` arrays to prevent accidental cross-interface mutations.
105
109
  * Many entity definitions reuse the same array instances across create/update/single interfaces.
@@ -112,9 +116,19 @@ function cloneLayoutArrays(layout) {
112
116
  };
113
117
  }
114
118
  function ensureLayoutSection(layout, section) {
115
- if (!layout.sections.some((s) => s.id === section.id)) {
119
+ const existingIndex = layout.sections.findIndex((s) => s.id === section.id);
120
+ if (existingIndex === -1) {
116
121
  layout.sections = [...layout.sections, section];
122
+ return;
123
+ }
124
+ const incomingOrder = section.order;
125
+ if (incomingOrder == null) {
126
+ return;
117
127
  }
128
+ const existing = layout.sections[existingIndex];
129
+ const mergedOrder = Math.max(existing.order ?? 0, incomingOrder);
130
+ layout.sections = [...layout.sections];
131
+ layout.sections[existingIndex] = { ...existing, ...section, order: mergedOrder };
118
132
  }
119
133
  function ensureLayoutPropertyView(layout, prop) {
120
134
  if (!layout.properties.some((p) => p.name === prop.name)) {
@@ -858,39 +872,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
858
872
  }]
859
873
  }] });
860
874
 
861
- /**
862
- * Computes a diff between two plain objects with array-aware semantics.
863
- * - For arrays of objects with an id field, computes added/removed by id.
864
- * - For arrays of primitives or objects without id, uses deep equality.
865
- * - For scalars/objects, reports oldValue/newValue when changed.
866
- */
867
- function detectEntityChanges(oldObj, newObj) {
868
- return transform(newObj, (result, value, key) => {
869
- if (!isEqual(value, oldObj[key])) {
870
- const oldValue = oldObj[key];
871
- if (Array.isArray(value) || Array.isArray(oldValue)) {
872
- const oldArray = Array.isArray(oldValue) ? oldValue : [];
873
- const newArray = Array.isArray(value) ? value : [];
874
- const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
875
- if (hasId) {
876
- const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
877
- const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
878
- result[key] = { oldValue, newValue: value, added, removed };
879
- }
880
- else {
881
- const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual(item, oldItem)));
882
- const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual(item, newItem)));
883
- result[key] = { oldValue, newValue: value, added, removed };
884
- }
885
- }
886
- else {
887
- result[key] = { oldValue, newValue: value };
888
- }
889
- }
890
- }, {});
891
- }
892
- //#endregion
893
-
894
875
  /**
895
876
  * Maps entity property `description` (or any i18n key / multi-language payload) to `form-field`
896
877
  * widget options. Returns `undefined` when there is nothing to show.
@@ -4220,7 +4201,6 @@ class AXPEntitiesListDataSourceDefinition {
4220
4201
  }
4221
4202
  }
4222
4203
 
4223
- //#region ---- Imports ----
4224
4204
  //#endregion
4225
4205
  //#region ---- Constants ----
4226
4206
  const DEFAULT_RELATED_ORDER_STEP = 10;
@@ -4356,7 +4336,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4356
4336
 
4357
4337
  class AXPEntityCommandTriggerViewModel {
4358
4338
  constructor(entity, action, isChild = false) {
4359
- this.name = `${typeof action.command === 'string' ? action.command : action.command.name}&${action.name}`;
4339
+ this.name = AXPEntityCommandTriggerViewModel.buildTriggerName(action);
4360
4340
  this.options = typeof action.command === 'object' ? action.command.options : undefined;
4361
4341
  this.metadata = typeof action.command === 'object' ? action.command.metadata : undefined;
4362
4342
  const resolvedLook = resolveActionLook(action.type);
@@ -4374,6 +4354,15 @@ class AXPEntityCommandTriggerViewModel {
4374
4354
  this.isChild = isChild;
4375
4355
  this.items = action.items?.map((child) => new AXPEntityCommandTriggerViewModel(entity, child, true));
4376
4356
  }
4357
+ /** Stable trigger id for command lookup and list tracking when {@link AXPEntityAction.name} is omitted. */
4358
+ static buildTriggerName(action) {
4359
+ const commandName = typeof action.command === 'string' ? action.command : action.command.name;
4360
+ if (action.name) {
4361
+ return `${commandName}&${action.name}`;
4362
+ }
4363
+ const suffix = [action.scope, action.priority, String(action.order ?? 0), action.type].join(':');
4364
+ return `${commandName}&${suffix}`;
4365
+ }
4377
4366
  }
4378
4367
  class AXPEntityListViewColumnViewModel {
4379
4368
  constructor(property, column) {
@@ -5332,6 +5321,48 @@ class AXPEntityMasterListViewModel {
5332
5321
  const resolved = String(result).trim();
5333
5322
  return resolved !== '' ? resolved : 'default';
5334
5323
  }
5324
+ /**
5325
+ * Resolves table row CSS classes from `layouts.table.cssClass.row` for the given row.
5326
+ */
5327
+ async resolveTableRowCssClass(rowData) {
5328
+ return this.resolveTableCssClassValue(this.tableLayout()?.cssClass?.row, rowData);
5329
+ }
5330
+ /**
5331
+ * Resolves table cell CSS classes from `layouts.table.cssClass.cell` for the given row and column.
5332
+ */
5333
+ async resolveTableCellCssClass(rowData, columnName) {
5334
+ return this.resolveTableCssClassValue(this.tableLayout()?.cssClass?.cell, rowData, columnName);
5335
+ }
5336
+ createTableCssClassExpressionScope(data, columnName) {
5337
+ const scope = this.createLogicExpressionScope(data);
5338
+ if (!columnName) {
5339
+ return scope;
5340
+ }
5341
+ const columnContext = { name: columnName };
5342
+ return {
5343
+ ...scope,
5344
+ table: {
5345
+ getColumn: (field) => {
5346
+ return get(columnContext, field);
5347
+ },
5348
+ },
5349
+ };
5350
+ }
5351
+ async resolveTableCssClassValue(expression, rowData, columnName) {
5352
+ if (!expression) {
5353
+ return undefined;
5354
+ }
5355
+ const scope = this.createTableCssClassExpressionScope(rowData, columnName);
5356
+ const result = await this.expressionEvaluator.evaluate(expression, scope);
5357
+ return this.normalizeTableCssClassValue(result);
5358
+ }
5359
+ normalizeTableCssClassValue(value) {
5360
+ if (value === false || value == null) {
5361
+ return undefined;
5362
+ }
5363
+ const resolved = String(value).trim();
5364
+ return resolved !== '' ? resolved : undefined;
5365
+ }
5335
5366
  async resolveTriggerName(triggerName, data) {
5336
5367
  if (!triggerName) {
5337
5368
  return triggerName;
@@ -5495,6 +5526,8 @@ class AXPEntityMasterListViewModel {
5495
5526
  this.hasInlineFilters = computed(() => {
5496
5527
  return this.inlineFiltersPlaceholders().length > 0;
5497
5528
  }, ...(ngDevMode ? [{ debugName: "hasInlineFilters" }] : /* istanbul ignore next */ []));
5529
+ /** Text for data-table cell highlighting (set only after filters are applied). */
5530
+ this.inlineFilterHighlightText = signal('', ...(ngDevMode ? [{ debugName: "inlineFilterHighlightText" }] : /* istanbul ignore next */ []));
5498
5531
  //****************** Beardcrumbs ******************//
5499
5532
  this.beardcrumbs = computed(() => {
5500
5533
  return [
@@ -5508,7 +5541,7 @@ class AXPEntityMasterListViewModel {
5508
5541
  const list = this.entityDef.interfaces?.master?.list?.actions ?? [];
5509
5542
  const actions = list.map((tr) => new AXPEntityCommandTriggerViewModel(this.entityDef, tr)) ?? [];
5510
5543
  const orderedActions = orderBy(actions, 'order', 'asc');
5511
- return orderedActions;
5544
+ return this.dedupeActionsByName(orderedActions);
5512
5545
  }, ...(ngDevMode ? [{ debugName: "allActions" }] : /* istanbul ignore next */ []));
5513
5546
  /** Flattened actions including children for execute lookup */
5514
5547
  this.flattenedActions = computed(() => {
@@ -5986,6 +6019,17 @@ class AXPEntityMasterListViewModel {
5986
6019
  setSelectedCategory(category) {
5987
6020
  this.selectedCategory.set(category);
5988
6021
  }
6022
+ /** Keeps the first action when list definitions repeat the same trigger name. */
6023
+ dedupeActionsByName(actions) {
6024
+ const seen = new Set();
6025
+ return actions.filter((action) => {
6026
+ if (seen.has(action.name)) {
6027
+ return false;
6028
+ }
6029
+ seen.add(action.name);
6030
+ return true;
6031
+ });
6032
+ }
5989
6033
  async getPrimaryActions() {
5990
6034
  const scope = this.createLogicExpressionScope(null);
5991
6035
  const actions = await Promise.all(this.allActions()
@@ -6242,6 +6286,7 @@ class AXPEntityMasterListViewModel {
6242
6286
  if (sortKey === this.lastAppliedSortKey && filterKey === this.lastAppliedFilterKey) {
6243
6287
  return; // No effective change; avoid redundant refresh
6244
6288
  }
6289
+ this.updateAppliedInlineFilterHighlightText(filters);
6245
6290
  const filtersChanged = this.lastAppliedFilterKey != null && filterKey !== this.lastAppliedFilterKey;
6246
6291
  this.lastAppliedSortKey = sortKey;
6247
6292
  this.lastAppliedFilterKey = filterKey;
@@ -6253,6 +6298,11 @@ class AXPEntityMasterListViewModel {
6253
6298
  this.dataSource.filter(transformedFilter);
6254
6299
  this.events$.next({ action: 'refresh', meta: { resetPagination: filtersChanged } });
6255
6300
  }
6301
+ updateAppliedInlineFilterHighlightText(filters) {
6302
+ const allFilter = filters.find((f) => f.field === 'all');
6303
+ const value = allFilter?.value;
6304
+ this.inlineFilterHighlightText.set(typeof value === 'string' ? value.trim() : '');
6305
+ }
6256
6306
  //****************** Columns ******************//
6257
6307
  resolveMergeDetailProperty(columnName) {
6258
6308
  const parsed = parseDottedFieldPath(columnName);
@@ -7684,10 +7734,11 @@ async function calculateChildrenCounts(items, entityName, getDirectChildCount) {
7684
7734
  * Loads raw rows for an entity and applies the same sorting and filtering as {@link runEntityQuery},
7685
7735
  * without pagination.
7686
7736
  */
7687
- async function filterSortEntityRows(entityName, request, adapters) {
7737
+ async function filterSortEntityRows(entityName, request, adapters, sortLocale) {
7688
7738
  let result = await adapters.getRawAll(entityName);
7689
7739
  if (request.sort && request.sort.length) {
7690
- result = applySortArray(result, request.sort);
7740
+ const locale = sortLocale ?? request.params?.['locale'] ?? 'en-US';
7741
+ result = applySortArray(result, request.sort, locale);
7691
7742
  }
7692
7743
  if (request.filter && isCategoryFilter(request.filter)) {
7693
7744
  result = await applyRecursiveCategoryFilter(result, request.filter, entityName, adapters.getAllChildCategoryIds);
@@ -7703,8 +7754,8 @@ async function filterSortEntityRows(entityName, request, adapters) {
7703
7754
  /**
7704
7755
  * Shared entity query logic: sort, filter (including recursive category filter), childrenCount, pagination.
7705
7756
  */
7706
- async function runEntityQuery(entityName, request, adapters) {
7707
- const rows = await filterSortEntityRows(entityName, request, adapters);
7757
+ async function runEntityQuery(entityName, request, adapters, sortLocale) {
7758
+ const rows = await filterSortEntityRows(entityName, request, adapters, sortLocale);
7708
7759
  const skip = request.skip ?? 0;
7709
7760
  const take = request.take ?? 0;
7710
7761
  return {
@@ -12753,7 +12804,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12753
12804
  async reapplyScopedFiltersToDataSource() {
12754
12805
  const merged = this.getMergedToolbarFilters();
12755
12806
  const current = this.getValue();
12756
- if (!isEqual$1(current?.toolbar?.filters, merged)) {
12807
+ if (!isEqual(current?.toolbar?.filters, merged)) {
12757
12808
  this.setValue({
12758
12809
  ...current,
12759
12810
  toolbar: { ...(current?.toolbar ?? {}), filters: merged },
@@ -12800,7 +12851,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12800
12851
  return;
12801
12852
  }
12802
12853
  const current = this.getValue();
12803
- if (isEqual$1(current?.toolbar?.filters, filters)) {
12854
+ if (isEqual(current?.toolbar?.filters, filters)) {
12804
12855
  return;
12805
12856
  }
12806
12857
  this.setValue({
@@ -13897,31 +13948,6 @@ function compactLookupCreateContext(data) {
13897
13948
  }
13898
13949
  //#endregion
13899
13950
 
13900
- //#endregion
13901
- //#region ---- Resolution ----
13902
- /**
13903
- * Resolves lookup create options from `create` and legacy `allowCreate` (mode).
13904
- */
13905
- function resolveLookupWidgetCreateOptions(options) {
13906
- const create = options.create;
13907
- const legacyMode = options.allowCreate;
13908
- const context = create?.context;
13909
- if (create?.enable !== undefined) {
13910
- if (!create.enable) {
13911
- return { enabled: false, mode: 'none', context };
13912
- }
13913
- const mode = legacyMode && legacyMode !== 'none' ? legacyMode : 'quick';
13914
- return { enabled: true, mode, context };
13915
- }
13916
- const mode = legacyMode ?? 'none';
13917
- return {
13918
- enabled: mode !== 'none',
13919
- mode,
13920
- context,
13921
- };
13922
- }
13923
- //#endregion
13924
-
13925
13951
  //#endregion
13926
13952
  class AXPEntityDataSelectorRowActionsService {
13927
13953
  constructor() {
@@ -14934,10 +14960,20 @@ class AXPLookupWidgetSelectComponent extends LookupWidgetLookBase {
14934
14960
  }
14935
14961
  //#endregion
14936
14962
  clear() {
14963
+ AXPDebugService.instance?.trace(WIDGET_CASCADE_TRACE_NS, 'lookup-select', 'clear() — value cleared', () => ({
14964
+ entity: this.entityDef()()?.name,
14965
+ }));
14937
14966
  this.valueChanged.emit(null);
14938
14967
  }
14939
14968
  refresh() {
14940
- this.clear();
14969
+ AXPDebugService.instance?.trace(WIDGET_CASCADE_TRACE_NS, 'lookup-select', 'refresh()', () => ({
14970
+ entity: this.entityDef()()?.name,
14971
+ filter: this.customFilter()(),
14972
+ hasSelection: !isEmpty(this.selectedItemsValue()),
14973
+ }));
14974
+ if (!isEmpty(this.selectedItemsValue())) {
14975
+ this.clear();
14976
+ }
14941
14977
  this.dataSource()?.refresh();
14942
14978
  }
14943
14979
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetSelectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
@@ -15519,7 +15555,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15519
15555
  const prevValue = this.previousValue();
15520
15556
  const isInitialized = this.initialized();
15521
15557
  // Check if value has actually changed
15522
- const valueChanged = !isEqual$1(currentValue, prevValue);
15558
+ const valueChanged = !isEqual(currentValue, prevValue);
15523
15559
  // Determine if we should update:
15524
15560
  // 1. First initialization with a value
15525
15561
  // 2. Value becomes empty after initialization
@@ -15795,6 +15831,12 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
15795
15831
  }
15796
15832
  //#region ---- Component Methods ----
15797
15833
  refresh() {
15834
+ AXPDebugService.instance?.trace(WIDGET_CASCADE_TRACE_NS, 'lookup-widget', 'refresh()', () => ({
15835
+ path: this.fullPath(),
15836
+ entity: this.entity(),
15837
+ look: this.look(),
15838
+ customerPartyId: this.contextService.getValue('customer.partyId'),
15839
+ }));
15798
15840
  this.componentLook()?.refresh();
15799
15841
  }
15800
15842
  clear() {
@@ -16860,8 +16902,6 @@ const AXPLookupWidget = {
16860
16902
  },
16861
16903
  };
16862
16904
 
16863
- //#endregion
16864
-
16865
16905
  /**
16866
16906
  * Source type
16867
16907
  */
@@ -18927,7 +18967,7 @@ class AXPFileListComponent {
18927
18967
  return action.color ?? 'primary';
18928
18968
  }
18929
18969
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
18930
- 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:
18970
+ 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 } @else {\n <div class=\"__empty-state __empty-state--links\">\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 look=\"minimal\"\n />\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{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}.__empty-state--links{padding-top:.5rem;padding-bottom:.5rem}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
18931
18971
  // ACoreX
18932
18972
  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$2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type:
18933
18973
  // Platform
@@ -18955,7 +18995,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
18955
18995
  AXTranslationModule,
18956
18996
  // Platform
18957
18997
  AXPStateMessageComponent,
18958
- ], 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"] }]
18998
+ ], 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 } @else {\n <div class=\"__empty-state __empty-state--links\">\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 look=\"minimal\"\n />\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{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}.__empty-state--links{padding-top:.5rem;padding-bottom:.5rem}\n"] }]
18959
18999
  }], 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 }] }] } });
18960
19000
 
18961
19001
  //#region ---- Attachment fingerprint (no blob comparison) ----
@@ -19038,6 +19078,9 @@ function persistedAttachments(value) {
19038
19078
  }
19039
19079
  //#endregion
19040
19080
 
19081
+ const FILE_PREFIX = 'file:';
19082
+ const DOCUMENT_PREFIX = 'document:';
19083
+ const REFERENCE_PREFIX = 'reference:';
19041
19084
  //#endregion
19042
19085
  //#region ---- Helpers ----
19043
19086
  function isFileListItem(value) {
@@ -19095,6 +19138,120 @@ function attachmentFieldCount(fieldValue) {
19095
19138
  function normalizeEntityFieldToFileList(fieldValue) {
19096
19139
  return parseAttachmentFieldEntries(fieldValue).filter(isFileListItem);
19097
19140
  }
19141
+ /** Whether a stored value is a persisted file/document/reference token. */
19142
+ function isAttachmentStorageRef(value) {
19143
+ return (isString(value) &&
19144
+ (value.startsWith(FILE_PREFIX) ||
19145
+ value.startsWith(DOCUMENT_PREFIX) ||
19146
+ value.startsWith(REFERENCE_PREFIX)));
19147
+ }
19148
+ /** True when the field has entries that cannot be shown without hydration (e.g. mixed `file:` refs + objects). */
19149
+ function attachmentNeedsHydration(fieldValue) {
19150
+ const entries = parseAttachmentFieldEntries(fieldValue);
19151
+ const count = entries.filter(isAttachmentListEntry).length;
19152
+ if (count === 0) {
19153
+ return false;
19154
+ }
19155
+ const normalizedCount = entries.filter(isFileListItem).length;
19156
+ if (normalizedCount < count) {
19157
+ return true;
19158
+ }
19159
+ return entries.some(isAttachmentStorageRef);
19160
+ }
19161
+ function removeFilePrefix(prefixedId) {
19162
+ return prefixedId.startsWith(FILE_PREFIX) ? prefixedId.substring(FILE_PREFIX.length) : prefixedId;
19163
+ }
19164
+ function storageInfoToFileListItem(info) {
19165
+ const item = {
19166
+ id: info.fileId,
19167
+ name: info.name || 'Unknown',
19168
+ size: info.size,
19169
+ status: 'uploaded',
19170
+ source: { kind: 'fileId', value: info.fileId },
19171
+ };
19172
+ if (info.title !== undefined) {
19173
+ item.title = info.title;
19174
+ }
19175
+ if (info.description !== undefined) {
19176
+ item.description = info.description;
19177
+ }
19178
+ if (info.readOnly === true) {
19179
+ item.readOnly = true;
19180
+ }
19181
+ return item;
19182
+ }
19183
+ /**
19184
+ * Resolves inline attachment field values to displayable file list items.
19185
+ * Handles mixed arrays of `file:` refs and already-hydrated AXPFileListItem objects.
19186
+ */
19187
+ async function hydrateAttachmentFieldToFileList(fieldValue, fileStorage) {
19188
+ const entries = parseAttachmentFieldEntries(fieldValue).filter(isAttachmentListEntry);
19189
+ if (entries.length === 0) {
19190
+ return [];
19191
+ }
19192
+ const fileIds = entries
19193
+ .filter(isAttachmentStorageRef)
19194
+ .filter((ref) => ref.startsWith(FILE_PREFIX))
19195
+ .map(removeFilePrefix);
19196
+ const infoMap = new Map();
19197
+ if (fileIds.length > 0) {
19198
+ const infos = await fileStorage.findMany(fileIds);
19199
+ for (const info of infos) {
19200
+ infoMap.set(info.fileId, info);
19201
+ }
19202
+ }
19203
+ const result = [];
19204
+ for (const entry of entries) {
19205
+ if (isFileListItem(entry)) {
19206
+ result.push(entry);
19207
+ continue;
19208
+ }
19209
+ if (isAttachmentStorageRef(entry) && entry.startsWith(FILE_PREFIX)) {
19210
+ const id = removeFilePrefix(entry);
19211
+ const info = infoMap.get(id);
19212
+ if (info) {
19213
+ result.push(storageInfoToFileListItem(info));
19214
+ }
19215
+ else {
19216
+ result.push({
19217
+ id,
19218
+ name: 'Unknown File',
19219
+ status: 'remote',
19220
+ source: { kind: 'fileId', value: id },
19221
+ });
19222
+ }
19223
+ continue;
19224
+ }
19225
+ if (isAttachmentStorageRef(entry)) {
19226
+ result.push({
19227
+ id: entry,
19228
+ name: 'File',
19229
+ status: 'remote',
19230
+ source: { kind: 'fileId', value: entry },
19231
+ });
19232
+ }
19233
+ }
19234
+ return result;
19235
+ }
19236
+ /** Attachment field path from widget options or column path fallback. */
19237
+ function resolveAttachmentFieldPath(options, fieldFallback) {
19238
+ const entityBlock = get(options, 'entity');
19239
+ const fromEntityBlock = entityBlock != null && typeof entityBlock === 'object'
19240
+ ? get(entityBlock, 'field')
19241
+ : undefined;
19242
+ const field = get(options, 'entityField') ?? fromEntityBlock ?? fieldFallback;
19243
+ if (typeof field !== 'string' || !field.trim()) {
19244
+ return undefined;
19245
+ }
19246
+ return field.trim();
19247
+ }
19248
+ /** True when the list row includes the attachment field (even when empty). */
19249
+ function isAttachmentFieldPresentOnRow(rowData, fieldPath) {
19250
+ if (!fieldPath || !rowData) {
19251
+ return false;
19252
+ }
19253
+ return has(rowData, fieldPath);
19254
+ }
19098
19255
  /** Resolve entity scope for column popup / load when widget options carry list-row expressions. */
19099
19256
  function resolveEntityId(entityIdOption, rowData) {
19100
19257
  if (isString(entityIdOption) && entityIdOption.trim() && !entityIdOption.includes('{{')) {
@@ -19134,10 +19291,14 @@ function resolveFileUploaderEntityScope(options, rowData, fieldFallback) {
19134
19291
  }
19135
19292
  //#endregion
19136
19293
 
19294
+ /** Trace namespace for file-uploader attachment column, load/save, and hydration. */
19295
+ const FILE_UPLOADER_TRACE_NS = 'file-uploader-attachments';
19296
+
19137
19297
  class AXPFileUploaderLoadFilesQuery {
19138
19298
  constructor() {
19139
19299
  //#region ---- Services & Dependencies ----
19140
19300
  this.injector = inject(Injector);
19301
+ this.debug = inject(AXPDebugService);
19141
19302
  }
19142
19303
  //#endregion
19143
19304
  //#region ---- Query Execution ----
@@ -19151,8 +19312,28 @@ class AXPFileUploaderLoadFilesQuery {
19151
19312
  return dataService.getOne(entityId);
19152
19313
  });
19153
19314
  const fieldValue = get(record, field);
19154
- const files = normalizeEntityFieldToFileList(fieldValue);
19155
19315
  const count = attachmentFieldCount(fieldValue);
19316
+ const inlineFiles = normalizeEntityFieldToFileList(fieldValue);
19317
+ const needsHydration = attachmentNeedsHydration(fieldValue);
19318
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'load-files', 'Fetched entity attachment field', () => ({
19319
+ entityRef,
19320
+ entityId,
19321
+ field,
19322
+ count,
19323
+ inlineFiles: inlineFiles.length,
19324
+ needsHydration,
19325
+ }));
19326
+ let files = inlineFiles;
19327
+ if (needsHydration) {
19328
+ const fileStorage = runInInjectionContext(this.injector, () => inject(AXPFileStorageService));
19329
+ files = await hydrateAttachmentFieldToFileList(fieldValue, fileStorage);
19330
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'load-files', 'Hydrated attachment refs', () => ({
19331
+ entityId,
19332
+ field,
19333
+ hydratedCount: files.length,
19334
+ fileNames: files.map((f) => f.name),
19335
+ }));
19336
+ }
19156
19337
  return { files, count };
19157
19338
  }
19158
19339
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderLoadFilesQuery, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -19167,6 +19348,7 @@ class AXPFileUploaderSaveFilesCommand {
19167
19348
  constructor() {
19168
19349
  //#region ---- Services & Dependencies ----
19169
19350
  this.injector = inject(Injector);
19351
+ this.debug = inject(AXPDebugService);
19170
19352
  //#endregion
19171
19353
  }
19172
19354
  //#endregion
@@ -19174,6 +19356,14 @@ class AXPFileUploaderSaveFilesCommand {
19174
19356
  async execute(input) {
19175
19357
  const { name: entityRef, id: entityId, field } = input;
19176
19358
  const files = (input.files ?? []).filter(isFileListItem);
19359
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'save-files', 'Saving attachment field', () => ({
19360
+ entityRef,
19361
+ entityId,
19362
+ field,
19363
+ inputCount: input.files?.length ?? 0,
19364
+ savingCount: files.length,
19365
+ attachmentCount: attachmentFieldCount(files),
19366
+ }));
19177
19367
  if (!entityRef?.trim() || !entityId?.trim() || !field?.trim()) {
19178
19368
  return {
19179
19369
  success: false,
@@ -19213,7 +19403,6 @@ function hasFileUploaderTitleOrDescriptionFields(editDialog) {
19213
19403
  }
19214
19404
  //#endregion
19215
19405
 
19216
- //#region ---- Imports ----
19217
19406
  //#endregion
19218
19407
  //#region ---- Component ----
19219
19408
  class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
@@ -19226,23 +19415,32 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19226
19415
  this.entityResolver = inject(AXPEntityDefinitionRegistryService);
19227
19416
  this.formatService = inject(AXFormatService);
19228
19417
  this.translation = inject(AXTranslationService);
19418
+ this.debug = inject(AXPDebugService);
19229
19419
  //#endregion
19230
19420
  //#region ---- View Children ----
19231
19421
  this.fileListTrigger = viewChild('fileListTrigger', ...(ngDevMode ? [{ debugName: "fileListTrigger" }] : /* istanbul ignore next */ []));
19232
19422
  this.fileListPopover = viewChild('fileListPopover', ...(ngDevMode ? [{ debugName: "fileListPopover" }] : /* istanbul ignore next */ []));
19233
19423
  //#endregion
19234
19424
  //#region ---- State ----
19235
- this.fileCount = signal(0, ...(ngDevMode ? [{ debugName: "fileCount" }] : /* istanbul ignore next */ []));
19236
19425
  this.popoverFiles = signal([], ...(ngDevMode ? [{ debugName: "popoverFiles" }] : /* istanbul ignore next */ []));
19237
19426
  this.loadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "loadStatus" }] : /* istanbul ignore next */ []));
19238
19427
  this.loadError = signal(null, ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
19239
19428
  this.isPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPopoverOpen" }] : /* istanbul ignore next */ []));
19240
19429
  this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : /* istanbul ignore next */ []));
19241
- this.loadRequestId = 0;
19242
19430
  this.popoverLoadRequestId = 0;
19243
19431
  //#endregion
19244
19432
  //#region ---- Computed ----
19245
19433
  this.entityScope = computed(() => resolveFileUploaderEntityScope(this.options ?? {}, this.rowData, this.path), ...(ngDevMode ? [{ debugName: "entityScope" }] : /* istanbul ignore next */ []));
19434
+ this.attachmentFieldPath = computed(() => resolveAttachmentFieldPath(this.options ?? {}, this.path), ...(ngDevMode ? [{ debugName: "attachmentFieldPath" }] : /* istanbul ignore next */ []));
19435
+ this.inlineFieldPresent = computed(() => isAttachmentFieldPresentOnRow(this.rowData, this.attachmentFieldPath()), ...(ngDevMode ? [{ debugName: "inlineFieldPresent" }] : /* istanbul ignore next */ []));
19436
+ this.fileCount = computed(() => {
19437
+ if (!this.inlineFieldPresent()) {
19438
+ return 0;
19439
+ }
19440
+ return attachmentFieldCount(this.rawValue);
19441
+ }, ...(ngDevMode ? [{ debugName: "fileCount" }] : /* istanbul ignore next */ []));
19442
+ /** Clickable when count is unknown or zero — opens popover (lazy load or inline empty). */
19443
+ this.showAttachmentsLink = computed(() => this.fileCount() === 0 && this.entityScope() != null, ...(ngDevMode ? [{ debugName: "showAttachmentsLink" }] : /* istanbul ignore next */ []));
19246
19444
  this.popoverTitle = computed(() => {
19247
19445
  const row = this.rowData;
19248
19446
  const lang = this.translation.getActiveLang();
@@ -19259,10 +19457,7 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19259
19457
  return '@document-management:terms.common.file';
19260
19458
  }, ...(ngDevMode ? [{ debugName: "popoverTitle" }] : /* istanbul ignore next */ []));
19261
19459
  effect(() => {
19262
- const raw = this.rawValue;
19263
19460
  const row = this.rowData;
19264
- const opts = this.options ?? {};
19265
- const path = this.path;
19266
19461
  const rowId = row?.['id'] != null ? String(row['id']) : undefined;
19267
19462
  if (rowId !== this.previousRowId) {
19268
19463
  this.previousRowId = rowId;
@@ -19273,33 +19468,6 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19273
19468
  this.popoverLoadRequestId++;
19274
19469
  });
19275
19470
  }
19276
- const inlineCount = attachmentFieldCount(raw);
19277
- if (inlineCount > 0) {
19278
- untracked(() => this.fileCount.set(inlineCount));
19279
- return;
19280
- }
19281
- const entity = resolveFileUploaderEntityScope(opts, row, path);
19282
- if (!entity) {
19283
- untracked(() => this.fileCount.set(0));
19284
- return;
19285
- }
19286
- const requestId = ++this.loadRequestId;
19287
- untracked(() => {
19288
- void this.queryExecutor
19289
- .fetch('FileUploader:LoadFiles', entity)
19290
- .then((result) => {
19291
- if (requestId !== this.loadRequestId) {
19292
- return;
19293
- }
19294
- const count = result?.count ?? result?.files?.length ?? 0;
19295
- this.fileCount.set(count);
19296
- })
19297
- .catch(() => {
19298
- if (requestId === this.loadRequestId) {
19299
- this.fileCount.set(0);
19300
- }
19301
- });
19302
- });
19303
19471
  });
19304
19472
  effect(() => {
19305
19473
  const scope = this.entityScope();
@@ -19338,25 +19506,45 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19338
19506
  if (this.loadStatus() === 'loading') {
19339
19507
  return;
19340
19508
  }
19509
+ const count = attachmentFieldCount(this.rawValue);
19341
19510
  const inline = normalizeEntityFieldToFileList(this.rawValue);
19342
- if (inline.length > 0) {
19511
+ const needsHydration = attachmentNeedsHydration(this.rawValue);
19512
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'column-popover', 'Ensure popover files', () => ({
19513
+ rowId: this.previousRowId,
19514
+ inlineFieldPresent: this.inlineFieldPresent(),
19515
+ count,
19516
+ inlineFiles: inline.length,
19517
+ needsHydration,
19518
+ loadStatus: this.loadStatus(),
19519
+ }));
19520
+ if (inline.length > 0 && !needsHydration) {
19343
19521
  this.popoverFiles.set(inline);
19344
19522
  this.loadStatus.set('ready');
19345
19523
  this.loadError.set(null);
19346
19524
  return;
19347
19525
  }
19526
+ if (this.inlineFieldPresent() && count === 0) {
19527
+ this.popoverFiles.set([]);
19528
+ this.loadStatus.set('ready');
19529
+ this.loadError.set(null);
19530
+ return;
19531
+ }
19348
19532
  const entity = this.entityScope();
19349
19533
  if (!entity) {
19350
19534
  this.popoverFiles.set([]);
19351
19535
  this.loadStatus.set('ready');
19352
19536
  return;
19353
19537
  }
19354
- if (this.loadStatus() === 'ready' && this.popoverFiles().length > 0) {
19538
+ if (this.loadStatus() === 'ready' && this.popoverFiles().length > 0 && !needsHydration) {
19355
19539
  return;
19356
19540
  }
19357
19541
  const requestId = ++this.popoverLoadRequestId;
19358
19542
  this.loadStatus.set('loading');
19359
19543
  this.loadError.set(null);
19544
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'column-popover', 'Loading files via FileUploader:LoadFiles', () => ({
19545
+ entity,
19546
+ reason: needsHydration ? 'hydration-required' : 'lazy-or-empty',
19547
+ }));
19360
19548
  try {
19361
19549
  const result = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
19362
19550
  if (requestId !== this.popoverLoadRequestId) {
@@ -19364,11 +19552,19 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19364
19552
  }
19365
19553
  this.popoverFiles.set(result?.files ?? []);
19366
19554
  this.loadStatus.set('ready');
19555
+ this.debug.trace(FILE_UPLOADER_TRACE_NS, 'column-popover', 'Popover files loaded', () => ({
19556
+ loadedCount: result?.files?.length ?? 0,
19557
+ expectedCount: result?.count ?? 0,
19558
+ }));
19367
19559
  }
19368
- catch {
19560
+ catch (error) {
19369
19561
  if (requestId !== this.popoverLoadRequestId) {
19370
19562
  return;
19371
19563
  }
19564
+ this.debug.warn(FILE_UPLOADER_TRACE_NS, 'column-popover', 'Failed to load popover files', () => ({
19565
+ entity,
19566
+ error: error instanceof Error ? error.message : String(error),
19567
+ }));
19372
19568
  const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
19373
19569
  this.loadError.set(msg);
19374
19570
  this.loadStatus.set('error');
@@ -19391,11 +19587,11 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
19391
19587
  return false;
19392
19588
  }
19393
19589
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
19394
- 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$3.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: i3$1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19590
+ 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\n class=\"ax-cursor-pointer ax-text-primary ax-underline\"\n (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger\n >\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n </span>\n} @else if (showAttachmentsLink()) {\n <span\n class=\"ax-cursor-pointer ax-text-primary ax-underline\"\n (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger\n >\n {{ '@general:widgets.file-uploader.attachments.title' | translate | async }}\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 if (popoverFiles().length === 0) {\n <p class=\"axp-file-uploader-column-popover__empty\">\n {{ '@general:widgets.file-uploader.empty-state.title' | translate | async }}\n </p>\n } @else {\n <axp-file-list\n [files]=\"popoverFiles()\"\n look=\"links\"\n linksLayout=\"menu\"\n [showLabel]=\"false\"\n [readonly]=\"true\"\n />\n }\n </div>\n </div>\n</ax-popover>\n", styles: [".axp-file-uploader-column-popover__empty{margin:0;padding-top:.5rem;padding-bottom:.5rem;text-align:center;font-size:.875rem;line-height:1.25rem;line-height:1.375;color:rgb(var(--ax-sys-color-on-lighter-surface))}\n"], dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2$3.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: i3$1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
19395
19591
  }
19396
19592
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, decorators: [{
19397
19593
  type: Component,
19398
- 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>" }]
19594
+ 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\n class=\"ax-cursor-pointer ax-text-primary ax-underline\"\n (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger\n >\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n </span>\n} @else if (showAttachmentsLink()) {\n <span\n class=\"ax-cursor-pointer ax-text-primary ax-underline\"\n (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger\n >\n {{ '@general:widgets.file-uploader.attachments.title' | translate | async }}\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 if (popoverFiles().length === 0) {\n <p class=\"axp-file-uploader-column-popover__empty\">\n {{ '@general:widgets.file-uploader.empty-state.title' | translate | async }}\n </p>\n } @else {\n <axp-file-list\n [files]=\"popoverFiles()\"\n look=\"links\"\n linksLayout=\"menu\"\n [showLabel]=\"false\"\n [readonly]=\"true\"\n />\n }\n </div>\n </div>\n</ax-popover>\n", styles: [".axp-file-uploader-column-popover__empty{margin:0;padding-top:.5rem;padding-bottom:.5rem;text-align:center;font-size:.875rem;line-height:1.25rem;line-height:1.375;color:rgb(var(--ax-sys-color-on-lighter-surface))}\n"] }]
19399
19595
  }], ctorParameters: () => [], propDecorators: { fileListTrigger: [{ type: i0.ViewChild, args: ['fileListTrigger', { isSignal: true }] }], fileListPopover: [{ type: i0.ViewChild, args: ['fileListPopover', { isSignal: true }] }] } });
19400
19596
 
19401
19597
  var fileUploaderWidgetColumn_component = /*#__PURE__*/Object.freeze({
@@ -20124,16 +20320,21 @@ var index = /*#__PURE__*/Object.freeze({
20124
20320
  AXPFileUploaderWidgetService: AXPFileUploaderWidgetService,
20125
20321
  AXPFileUploaderWidgetViewComponent: AXPFileUploaderWidgetViewComponent,
20126
20322
  attachmentFieldCount: attachmentFieldCount,
20323
+ attachmentNeedsHydration: attachmentNeedsHydration,
20127
20324
  attachmentsSemanticallyEqual: attachmentsSemanticallyEqual,
20128
20325
  committedAttachments: committedAttachments,
20129
20326
  fingerprintAttachmentItem: fingerprintAttachmentItem,
20130
20327
  fingerprintAttachments: fingerprintAttachments,
20131
20328
  hasFileUploaderTitleOrDescriptionFields: hasFileUploaderTitleOrDescriptionFields,
20329
+ hydrateAttachmentFieldToFileList: hydrateAttachmentFieldToFileList,
20330
+ isAttachmentFieldPresentOnRow: isAttachmentFieldPresentOnRow,
20132
20331
  isAttachmentListEntry: isAttachmentListEntry,
20332
+ isAttachmentStorageRef: isAttachmentStorageRef,
20133
20333
  isFileListItem: isFileListItem,
20134
20334
  isFileUploaderEditDialogAuto: isFileUploaderEditDialogAuto,
20135
20335
  normalizeEntityFieldToFileList: normalizeEntityFieldToFileList,
20136
20336
  persistedAttachments: persistedAttachments,
20337
+ resolveAttachmentFieldPath: resolveAttachmentFieldPath,
20137
20338
  resolveFileUploaderEditDialog: resolveFileUploaderEditDialog,
20138
20339
  resolveFileUploaderEntityScope: resolveFileUploaderEntityScope
20139
20340
  });
@@ -20196,7 +20397,6 @@ var selectorStructureWidgetColumn_component = /*#__PURE__*/Object.freeze({
20196
20397
  AXPSelectorStructureWidgetColumnComponent: AXPSelectorStructureWidgetColumnComponent
20197
20398
  });
20198
20399
 
20199
- //#region ---- Imports ----
20200
20400
  /** Internal key for stable row tracking before a selector value is committed (persisted with the row). */
20201
20401
  const SELECTOR_STRUCTURE_ROW_UID = '__rowUid';
20202
20402
  //#endregion
@@ -25805,9 +26005,42 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
25805
26005
  AXPGetEntityDetailsQuery: AXPGetEntityDetailsQuery
25806
26006
  });
25807
26007
 
26008
+ /**
26009
+ * Computes a diff between two plain objects with array-aware semantics.
26010
+ * - For arrays of objects with an id field, computes added/removed by id.
26011
+ * - For arrays of primitives or objects without id, uses deep equality.
26012
+ * - For scalars/objects, reports oldValue/newValue when changed.
26013
+ */
26014
+ function detectEntityChanges(oldObj, newObj) {
26015
+ return transform(newObj, (result, value, key) => {
26016
+ if (!isEqual$1(value, oldObj[key])) {
26017
+ const oldValue = oldObj[key];
26018
+ if (Array.isArray(value) || Array.isArray(oldValue)) {
26019
+ const oldArray = Array.isArray(oldValue) ? oldValue : [];
26020
+ const newArray = Array.isArray(value) ? value : [];
26021
+ const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
26022
+ if (hasId) {
26023
+ const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
26024
+ const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
26025
+ result[key] = { oldValue, newValue: value, added, removed };
26026
+ }
26027
+ else {
26028
+ const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual$1(item, oldItem)));
26029
+ const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual$1(item, newItem)));
26030
+ result[key] = { oldValue, newValue: value, added, removed };
26031
+ }
26032
+ }
26033
+ else {
26034
+ result[key] = { oldValue, newValue: value };
26035
+ }
26036
+ }
26037
+ }, {});
26038
+ }
26039
+ //#endregion
26040
+
25808
26041
  /**
25809
26042
  * Generated bundle index. Do not edit.
25810
26043
  */
25811
26044
 
25812
- 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, 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, 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, canPersistEntityListState, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, commandMessageTextForError, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, formatLookupItemDisplayAsync, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, hasFileUploaderTitleOrDescriptionFields, isAXPMiddlewareAbortError, isAttachmentListEntry, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
26045
+ 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, 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_APPEARANCE_SECTION_ORDER, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_CLASSIFICATION_SECTION_ORDER, 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, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, EntityBuilder, EntityDataAccessor, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentNeedsHydration, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, canPersistEntityListState, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, commandMessageTextForError, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, formatLookupItemDisplayAsync, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, hasFileUploaderTitleOrDescriptionFields, hydrateAttachmentFieldToFileList, isAXPMiddlewareAbortError, isAttachmentFieldPresentOnRow, isAttachmentListEntry, isAttachmentStorageRef, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveAttachmentFieldPath, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
25813
26046
  //# sourceMappingURL=acorex-platform-layout-entity.mjs.map