@acorex/platform 21.0.0-next.39 → 21.0.0-next.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/fesm2022/acorex-platform-common.mjs +6 -2
  2. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  3. package/fesm2022/acorex-platform-core.mjs +8 -1
  4. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-domain.mjs +3 -0
  6. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-layout-builder.mjs +22 -5
  8. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-components.mjs +25 -13
  10. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-designer.mjs +203 -55
  12. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-entity.mjs +622 -121
  14. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-widget-core.mjs +169 -85
  16. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  17. package/fesm2022/acorex-platform-layout-widgets.mjs +643 -311
  18. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-runtime.mjs +120 -9
  20. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  21. package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs → acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs} +3 -3
  22. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs.map +1 -0
  23. package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs → acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs} +3 -3
  24. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs.map +1 -0
  25. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs → acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs} +3 -3
  26. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-themes-default.mjs +10 -10
  28. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  29. package/fesm2022/{acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs → acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs} +2 -2
  30. package/fesm2022/acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs +94 -0
  32. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs.map +1 -0
  33. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs} +38 -16
  34. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-themes-shared.mjs +183 -84
  36. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  37. package/fesm2022/acorex-platform-workflow.mjs +50 -10
  38. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/types/acorex-platform-core.d.ts +13 -2
  41. package/types/acorex-platform-domain.d.ts +28 -2
  42. package/types/acorex-platform-layout-builder.d.ts +41 -27
  43. package/types/acorex-platform-layout-designer.d.ts +55 -15
  44. package/types/acorex-platform-layout-entity.d.ts +145 -11
  45. package/types/acorex-platform-layout-widget-core.d.ts +81 -68
  46. package/types/acorex-platform-layout-widgets.d.ts +25 -5
  47. package/types/acorex-platform-runtime.d.ts +156 -61
  48. package/types/acorex-platform-workflow.d.ts +37 -2
  49. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +0 -1
  50. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +0 -1
  51. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +0 -1
  52. package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
  53. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +0 -65
  54. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +0 -1
  55. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +0 -1
@@ -4,14 +4,14 @@ import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString }
4
4
  import * as i4$4 from '@acorex/platform/common';
5
5
  import { AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
6
6
  import * as i0 from '@angular/core';
7
- import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule, makeEnvironmentProviders } from '@angular/core';
7
+ import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, computed, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule, makeEnvironmentProviders } from '@angular/core';
8
8
  import { Subject, takeUntil } from 'rxjs';
9
9
  import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
10
- import { AXPDeviceService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition, AXPSystemActionType } from '@acorex/platform/core';
10
+ import { AXPDeviceService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition, AXPSystemActionType } from '@acorex/platform/core';
11
11
  import { merge, get, castArray, cloneDeep, set, orderBy, omit, isNil, isEmpty, isEqual } from 'lodash-es';
12
12
  import { AXPSessionService, AXPAuthGuard, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
13
13
  import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
14
- import { defineCommand, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
14
+ import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
15
15
  import * as i1 from '@acorex/components/button';
16
16
  import { AXButtonModule } from '@acorex/components/button';
17
17
  import * as i4 from '@acorex/components/loading';
@@ -119,6 +119,28 @@ const AXP_ENTITY_ACTION_PLUGIN = new InjectionToken('AXP_ENTITY_ACTION_PLUGIN');
119
119
  function createModifierContext(entity) {
120
120
  const ctx = {
121
121
  entity,
122
+ plugins: {
123
+ list: () => entity.plugins ?? [],
124
+ add: (...items) => {
125
+ entity.plugins ??= [];
126
+ entity.plugins.push(...items);
127
+ return ctx;
128
+ },
129
+ remove: (predicate) => {
130
+ if (entity.plugins)
131
+ entity.plugins = entity.plugins.filter((p) => !predicate(p));
132
+ return ctx;
133
+ },
134
+ find: (name) => ({
135
+ get: () => entity.plugins?.find((p) => p.name === name),
136
+ update: (updater) => {
137
+ const index = entity.plugins?.findIndex((p) => p.name === name);
138
+ if (index !== undefined && index !== -1 && entity.plugins)
139
+ entity.plugins[index] = updater(entity.plugins[index]);
140
+ return ctx;
141
+ },
142
+ }),
143
+ },
122
144
  title: {
123
145
  get: () => entity.title,
124
146
  set: (newTitle) => {
@@ -781,6 +803,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
781
803
  }]
782
804
  }] });
783
805
 
806
+ /**
807
+ * Maps entity property `description` (or any i18n key / multi-language payload) to `form-field`
808
+ * widget options. Returns `undefined` when there is nothing to show.
809
+ */
810
+ function hintFormFieldOptionsFromDescription(description) {
811
+ if (description == null) {
812
+ return undefined;
813
+ }
814
+ if (typeof description === 'string' && description.trim() === '') {
815
+ return undefined;
816
+ }
817
+ return { hint: description, hintDisplayMode: 'icon' };
818
+ }
819
+ //#endregion
820
+
784
821
  //#endregion
785
822
  class AXPEntityFormBuilderService {
786
823
  constructor() {
@@ -1190,6 +1227,17 @@ class PropertyFilter {
1190
1227
  fs.setLook('fieldset');
1191
1228
  fs.setTitle((getGroupTitleFromList(allGroups, groupId) || groupId));
1192
1229
  fs.setCols(12);
1230
+ // Section label visibility (fieldset title/legend)
1231
+ // Prefer current interface (create/update/single) section layout, fallback to master.single section layout.
1232
+ const sectionLabelVisible = section?.layout?.label?.visible ??
1233
+ entity?.interfaces?.master?.single?.sections?.find((s) => s?.id === groupId)?.layout?.label
1234
+ ?.visible;
1235
+ if (sectionLabelVisible === false) {
1236
+ // Different parts of the system refer to this concept with different keys.
1237
+ // - Fieldset view renderer currently uses `showTitle`
1238
+ // - Fieldset designer/property system uses `showHeader`
1239
+ fs.setOptions({ showTitle: false, showHeader: false });
1240
+ }
1193
1241
  // Sort properties by order within section
1194
1242
  const orderedProps = [...sectionProps].sort((a, b) => {
1195
1243
  const aOrder = a.__order ?? Infinity;
@@ -1220,6 +1268,10 @@ class PropertyFilter {
1220
1268
  if (fieldLayout?.label?.visible === false) {
1221
1269
  field.setShowLabel(false);
1222
1270
  }
1271
+ const hintOpts = hintFormFieldOptionsFromDescription(prop.description);
1272
+ if (hintOpts) {
1273
+ field.setOptions(hintOpts);
1274
+ }
1223
1275
  const widgetType = prop.schema?.interface?.type || '';
1224
1276
  const widgetOptions = buildWidgetOptions(prop);
1225
1277
  const extendedProperties = buildWidgetExtendedProperties(prop);
@@ -1911,6 +1963,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
1911
1963
  summary: 'Dialog submit persists entity; navigation/toast behavior per entity form builder and settings.',
1912
1964
  derivesFrom: 'AXPExecuteCommandResult<any> from entity form chain and AXPOpenEntityDetailsCommand',
1913
1965
  },
1966
+ capabilities: ['ai'],
1914
1967
  ai: {
1915
1968
  shortDescription: 'Opens create dialog with pre-filled data; execution waits until the user submits or cancels, then returns—use for human-confirmed inserts into mock storage.',
1916
1969
  usage: {
@@ -1925,6 +1978,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
1925
1978
  tags: ['entity', 'create', 'form', 'dialog', 'human-in-the-loop', 'mock', 'entity-storage', 'wait-user'],
1926
1979
  toolInputDefaults: { ...axpCreateEntityAiToolInputDefaults },
1927
1980
  },
1981
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_ENTITY],
1928
1982
  });
1929
1983
 
1930
1984
  class AXPUpdateEntityCommand {
@@ -2193,6 +2247,18 @@ class AXPEntityDetailPopoverComponent {
2193
2247
  this.entityDetails = signal(null, ...(ngDevMode ? [{ debugName: "entityDetails" }] : /* istanbul ignore next */ []));
2194
2248
  this.isLoadingDetails = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingDetails" }] : /* istanbul ignore next */ []));
2195
2249
  this.isDetailPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDetailPopoverOpen" }] : /* istanbul ignore next */ []));
2250
+ /**
2251
+ * Stable list of property widgets for the template. Must be a signal (computed), not a method:
2252
+ * calling a method from the template rebuilds nodes every CD cycle and can make the widget renderer loop.
2253
+ */
2254
+ this.entityPropertiesWithWidgets = computed(() => {
2255
+ const details = this.entityDetails();
2256
+ const data = details?.entityData;
2257
+ const entityDefinition = details?.entityDefinition;
2258
+ if (!data || !entityDefinition?.properties)
2259
+ return [];
2260
+ return this.buildEntityPropertiesWithWidgets(data, entityDefinition, this.textField(), this.valueField());
2261
+ }, ...(ngDevMode ? [{ debugName: "entityPropertiesWithWidgets" }] : /* istanbul ignore next */ []));
2196
2262
  }
2197
2263
  //#endregion
2198
2264
  //#region ---- Public Methods ----
@@ -2347,16 +2413,12 @@ class AXPEntityDetailPopoverComponent {
2347
2413
  }
2348
2414
  return 0;
2349
2415
  }
2350
- getEntityPropertiesWithWidgets() {
2351
- const data = this.entityDetails()?.entityData;
2352
- const entityDefinition = this.entityDetails()?.entityDefinition;
2353
- if (!data || !entityDefinition?.properties)
2354
- return [];
2416
+ buildEntityPropertiesWithWidgets(data, entityDefinition, textFieldValue, valueFieldValue) {
2355
2417
  // Use properties (not columns) for correct titles and schema; columns may have dataPath that doesn't match
2356
- const importantProperties = entityDefinition.properties
2418
+ return entityDefinition.properties
2357
2419
  .filter((prop) => {
2358
2420
  // Exclude technical fields
2359
- if (prop.name === 'id' || prop.name === this.textField() || prop.name === this.valueField()) {
2421
+ if (prop.name === 'id' || prop.name === textFieldValue || prop.name === valueFieldValue) {
2360
2422
  return false;
2361
2423
  }
2362
2424
  // Only include properties that have a meaningful value (using get for dotted paths)
@@ -2424,7 +2486,6 @@ class AXPEntityDetailPopoverComponent {
2424
2486
  node: widgetNode,
2425
2487
  };
2426
2488
  });
2427
- return importantProperties;
2428
2489
  }
2429
2490
  /**
2430
2491
  * Resolves the data path for a property. For lookups with expose, returns the expanded path
@@ -2447,11 +2508,11 @@ class AXPEntityDetailPopoverComponent {
2447
2508
  return prop.name;
2448
2509
  }
2449
2510
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2450
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of getEntityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2511
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2451
2512
  }
2452
2513
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
2453
2514
  type: Component,
2454
- args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXLoadingModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of getEntityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n" }]
2515
+ args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXLoadingModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ entityDetails()?.entityData?.[textField()] ?? item()?.[textField()] | translate | async }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n" }]
2455
2516
  }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: true }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], breadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumb", required: false }] }], detailPopover: [{ type: i0.ViewChild, args: ['detailPopover', { isSignal: true }] }] } });
2456
2517
 
2457
2518
  class AXPEntityDetailPopoverService {
@@ -4151,6 +4212,7 @@ class AXPEntityMasterListViewModel {
4151
4212
  return this.sortedFields().filter((i) => i.dir).length;
4152
4213
  };
4153
4214
  this.sortedFields = signal([], ...(ngDevMode ? [{ debugName: "sortedFields" }] : /* istanbul ignore next */ []));
4215
+ //#endregion
4154
4216
  this.evaluateExpressions = async (options, data) => {
4155
4217
  const scope = this.createExpressionScope(data);
4156
4218
  return await this.expressionEvaluator.evaluate(options, scope);
@@ -4623,7 +4685,8 @@ class AXPEntityMasterListViewModel {
4623
4685
  const command = resolvedTriggerName.split('&')[0];
4624
4686
  const options = await this.evaluateExpressions(action?.options, data);
4625
4687
  const baseData = action?.scope == AXPEntityCommandScope.Selected ? this.selectedItems() : data;
4626
- const commandData = this.mergeDefaultCategoryForCreate(command, baseData);
4688
+ let commandData = this.mergeViewConditionsForCreate(command, baseData);
4689
+ commandData = this.mergeDefaultCategoryForCreate(command, commandData);
4627
4690
  if (this.workflow.exists(command)) {
4628
4691
  await this.workflow.execute(command, {
4629
4692
  entity: getEntityInfo(this.entityDef).source,
@@ -4705,6 +4768,38 @@ class AXPEntityMasterListViewModel {
4705
4768
  }
4706
4769
  return baseData;
4707
4770
  }
4771
+ //#region ---- Create command defaults from list view ----
4772
+ /**
4773
+ * Seeds create payloads with fixed list-view conditions (e.g. scope=T on the tenant dashboards view)
4774
+ * so new rows match the view context and downstream queries (such as home dashboard) can find them.
4775
+ */
4776
+ mergeViewConditionsForCreate(command, baseData) {
4777
+ const isCreateCommand = command === 'Entity:Create' || command === 'create-entity';
4778
+ if (!isCreateCommand) {
4779
+ return baseData;
4780
+ }
4781
+ const conditions = this.view()?.conditions ?? [];
4782
+ if (conditions.length === 0) {
4783
+ return baseData;
4784
+ }
4785
+ const seed = {};
4786
+ for (const c of conditions) {
4787
+ if (!c?.name || c.operator?.type !== 'equal' || c.value === undefined) {
4788
+ continue;
4789
+ }
4790
+ seed[c.name] = c.value;
4791
+ }
4792
+ if (Object.keys(seed).length === 0) {
4793
+ return baseData;
4794
+ }
4795
+ if (baseData == null) {
4796
+ return seed;
4797
+ }
4798
+ if (typeof baseData === 'object' && !Array.isArray(baseData)) {
4799
+ return { ...seed, ...baseData };
4800
+ }
4801
+ return baseData;
4802
+ }
4708
4803
  async execute(command) {
4709
4804
  switch (command?.name) {
4710
4805
  case 'navigate':
@@ -9858,7 +9953,7 @@ class AXPEntityListTableService {
9858
9953
  animation: true,
9859
9954
  },
9860
9955
  // 🎪 Events
9861
- ...this.createDefaultEvents(entity, allActions),
9956
+ ...this.createDefaultEvents(entity, allActions, options?.excludeProperties),
9862
9957
  };
9863
9958
  console.log('listOptions', listOptions);
9864
9959
  return listOptions;
@@ -9948,7 +10043,7 @@ class AXPEntityListTableService {
9948
10043
  /**
9949
10044
  * Handle execution of a row command (shared by double-click and command handlers)
9950
10045
  */
9951
- async handleRowCommand(e, selectedRows, entity, allActions) {
10046
+ async handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties) {
9952
10047
  const data = e.data;
9953
10048
  const commandName = e.name;
9954
10049
  const action = allActions.find((c) => {
@@ -9959,7 +10054,8 @@ class AXPEntityListTableService {
9959
10054
  c.scope == AXPEntityCommandScope.TypeLevel));
9960
10055
  });
9961
10056
  const command = commandName.split('&')[0];
9962
- const options = await this.evaluateExpressions(action?.options, data);
10057
+ const evaluatedOptions = await this.evaluateExpressions(action?.options, data);
10058
+ const options = this.mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties);
9963
10059
  if (this.commandService.exists(command)) {
9964
10060
  await this.commandService.execute(command, {
9965
10061
  __context__: {
@@ -9995,10 +10091,25 @@ class AXPEntityListTableService {
9995
10091
  });
9996
10092
  }
9997
10093
  }
10094
+ /**
10095
+ * When a related entity list declares `excludeProperties`, row commands bypass the details page
10096
+ * `execute()` merge — apply the same exclusions for embedded create/update commands.
10097
+ */
10098
+ mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties) {
10099
+ const exclusions = relatedListExcludeProperties?.filter(Boolean);
10100
+ if (!exclusions?.length ||
10101
+ (command !== 'Entity:Create' && command !== 'Entity:Update')) {
10102
+ return evaluatedOptions;
10103
+ }
10104
+ return {
10105
+ ...evaluatedOptions,
10106
+ excludeProperties: exclusions,
10107
+ };
10108
+ }
9998
10109
  /**
9999
10110
  * Create default events
10000
10111
  */
10001
- createDefaultEvents(entity, allActions) {
10112
+ createDefaultEvents(entity, allActions, relatedListExcludeProperties) {
10002
10113
  return {
10003
10114
  onRowClick: (row) => {
10004
10115
  console.log('Entity List - Row clicked:', row);
@@ -10017,13 +10128,13 @@ class AXPEntityListTableService {
10017
10128
  name: defaultAction.name,
10018
10129
  data: e.data,
10019
10130
  };
10020
- this.handleRowCommand(d, undefined, entity, allActions);
10131
+ this.handleRowCommand(d, undefined, entity, allActions, relatedListExcludeProperties);
10021
10132
  },
10022
10133
  onSelectionChange: (selectedRows) => {
10023
10134
  console.log('Entity List - Selection changed:', selectedRows);
10024
10135
  },
10025
10136
  onRowCommand: async (e, selectedRows) => {
10026
- await this.handleRowCommand(e, selectedRows, entity, allActions);
10137
+ await this.handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties);
10027
10138
  },
10028
10139
  };
10029
10140
  }
@@ -10380,7 +10491,12 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10380
10491
  const commandData = action?.scope == AXPEntityCommandScope.Selected
10381
10492
  ? this.selectedItems()
10382
10493
  : action?.options?.['process']?.data || null;
10383
- const options = await this.evaluateToolbarExpressions(action?.options, commandData);
10494
+ const evaluatedToolbarOptions = await this.evaluateToolbarExpressions(action?.options, commandData);
10495
+ const relatedExcludes = this.options()['excludeProperties'];
10496
+ const exclusions = relatedExcludes?.filter(Boolean);
10497
+ const options = exclusions?.length && (command === 'Entity:Create' || command === 'Entity:Update')
10498
+ ? { ...evaluatedToolbarOptions, excludeProperties: exclusions }
10499
+ : evaluatedToolbarOptions;
10384
10500
  if (this.commandService.exists(command)) {
10385
10501
  await this.commandService.execute(command, {
10386
10502
  __context__: {
@@ -10529,6 +10645,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10529
10645
  includeColumns: this.includeColumns(),
10530
10646
  relatedTableColumns: this.relatedTableColumns(),
10531
10647
  customFilterDefinitions: this.customFilterDefinitions(),
10648
+ excludeProperties: this.options()['excludeProperties'],
10532
10649
  };
10533
10650
  const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
10534
10651
  const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
@@ -10846,7 +10963,7 @@ const AXPEntityListWidget = {
10846
10963
  type: 'view',
10847
10964
  categories: [],
10848
10965
  groups: [AXPWidgetGroupEnum.EntityWidget],
10849
- icon: 'fa-solid fa-square',
10966
+ icon: 'fa-light fa-square',
10850
10967
  properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY],
10851
10968
  components: {
10852
10969
  view: {
@@ -12517,36 +12634,47 @@ var lookupWidgetEdit_component = /*#__PURE__*/Object.freeze({
12517
12634
 
12518
12635
  class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12519
12636
  constructor() {
12520
- super(...arguments);
12521
- //#region ---- Dependencies ----
12637
+ super();
12638
+ //#region ---- Dependencies ----
12522
12639
  this.entityDetailPopoverService = inject(AXPEntityDetailPopoverService);
12523
- this.mlResolver = inject(AXTranslationService);
12640
+ this.translation = inject(AXTranslationService);
12524
12641
  this.formatService = inject(AXFormatService);
12642
+ this.entityService = inject(AXPEntityService);
12643
+ this.queryExecutor = inject(AXPQueryExecutor);
12525
12644
  //#endregion
12526
- //#region ---- View Children ----
12645
+ //#region ---- View Children ----
12527
12646
  this.moreButton = viewChild('moreButton', ...(ngDevMode ? [{ debugName: "moreButton" }] : /* istanbul ignore next */ []));
12647
+ this.lazyTrigger = viewChild('lazyTrigger', ...(ngDevMode ? [{ debugName: "lazyTrigger" }] : /* istanbul ignore next */ []));
12528
12648
  this.morePopover = viewChild('morePopover', ...(ngDevMode ? [{ debugName: "morePopover" }] : /* istanbul ignore next */ []));
12529
12649
  //#endregion
12530
- //#region ---- Properties ----
12650
+ //#region ---- Properties ----
12531
12651
  this.host = inject(ElementRef);
12532
12652
  this.valueField = this.options['valueField'] ?? 'id';
12533
12653
  this.textField = this.options['textField'] ?? 'title';
12534
- this.entity = this.options['entity'] ?? 'title';
12654
+ this.entity = this.options['entity'] ?? '';
12535
12655
  this.columnName = this.options['columnName'] ?? 'title';
12536
12656
  this.maxVisible = this.options['maxVisible'] ?? 2;
12537
12657
  this.displayFormat = computed(() => {
12538
12658
  const template = this.options['displayFormat'];
12539
12659
  return template ? template.replace(/\{/g, '{{').replace(/\}/g, '}}') : undefined;
12540
12660
  }, ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
12541
- this.displayField = computed(() => {
12542
- return this.textField ?? 'title';
12543
- }, ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
12661
+ this.displayField = computed(() => this.textField ?? 'title', ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
12662
+ this.columnResolve = computed(() => this.options['columnResolve'], ...(ngDevMode ? [{ debugName: "columnResolve" }] : /* istanbul ignore next */ []));
12663
+ this.resolveStrategy = computed(() => {
12664
+ return this.columnResolve()?.strategy ?? 'hydrated';
12665
+ }, ...(ngDevMode ? [{ debugName: "resolveStrategy" }] : /* istanbul ignore next */ []));
12666
+ this.isHydratedStrategy = computed(() => this.resolveStrategy() === 'hydrated', ...(ngDevMode ? [{ debugName: "isHydratedStrategy" }] : /* istanbul ignore next */ []));
12544
12667
  //#endregion
12545
- //#region ---- Signals ----
12668
+ //#region ---- Signals ----
12546
12669
  this.isMorePopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMorePopoverOpen" }] : /* istanbul ignore next */ []));
12547
12670
  this.selectedItemIndex = signal(-1, ...(ngDevMode ? [{ debugName: "selectedItemIndex" }] : /* istanbul ignore next */ []));
12671
+ this.resolvedPopoverItems = signal([], ...(ngDevMode ? [{ debugName: "resolvedPopoverItems" }] : /* istanbul ignore next */ []));
12672
+ this.resolveStatus = signal('idle', ...(ngDevMode ? [{ debugName: "resolveStatus" }] : /* istanbul ignore next */ []));
12673
+ this.resolveError = signal(null, ...(ngDevMode ? [{ debugName: "resolveError" }] : /* istanbul ignore next */ []));
12674
+ this.summaryLabel = signal('', ...(ngDevMode ? [{ debugName: "summaryLabel" }] : /* istanbul ignore next */ []));
12675
+ this.popoverHeader = signal('', ...(ngDevMode ? [{ debugName: "popoverHeader" }] : /* istanbul ignore next */ []));
12548
12676
  //#endregion
12549
- //#region ---- Computed Properties ----
12677
+ //#region ---- Computed ----
12550
12678
  this.displayItems = computed(() => isNil(this.rawValue)
12551
12679
  ? []
12552
12680
  : castArray(this.rawValue)
@@ -12557,34 +12685,127 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12557
12685
  const items = this.allItems();
12558
12686
  return items.slice(0, this.maxVisible);
12559
12687
  }, ...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
12560
- this.hasMoreItems = computed(() => {
12561
- return this.allItems().length > this.maxVisible;
12562
- }, ...(ngDevMode ? [{ debugName: "hasMoreItems" }] : /* istanbul ignore next */ []));
12563
- this.remainingItemsCount = computed(() => {
12564
- return this.allItems().length - this.maxVisible;
12565
- }, ...(ngDevMode ? [{ debugName: "remainingItemsCount" }] : /* istanbul ignore next */ []));
12688
+ this.hasMoreItems = computed(() => this.allItems().length > this.maxVisible, ...(ngDevMode ? [{ debugName: "hasMoreItems" }] : /* istanbul ignore next */ []));
12689
+ this.idsFromRow = computed(() => {
12690
+ const cr = this.columnResolve();
12691
+ const path = cr?.idsPath;
12692
+ const source = path ? get(this.rowData, path) : this.rawValue;
12693
+ if (isNil(source)) {
12694
+ return [];
12695
+ }
12696
+ return castArray(source)
12697
+ .map((x) => (typeof x === 'object' && x != null ? get(x, this.valueField) : x))
12698
+ .filter((id) => id != null && id !== '');
12699
+ }, ...(ngDevMode ? [{ debugName: "idsFromRow" }] : /* istanbul ignore next */ []));
12700
+ this.displayCount = computed(() => {
12701
+ const cr = this.columnResolve();
12702
+ const countPath = cr?.countFieldPath;
12703
+ if (countPath) {
12704
+ const v = get(this.rowData, countPath);
12705
+ if (typeof v === 'number' && !Number.isNaN(v)) {
12706
+ return v;
12707
+ }
12708
+ if (typeof v === 'string' && v !== '') {
12709
+ const n = Number(v);
12710
+ return Number.isNaN(n) ? 0 : n;
12711
+ }
12712
+ return 0;
12713
+ }
12714
+ if (this.resolveStrategy() === 'idsWithCount') {
12715
+ return this.idsFromRow().length;
12716
+ }
12717
+ if (this.resolveStrategy() === 'countOnly') {
12718
+ return 0;
12719
+ }
12720
+ return 0;
12721
+ }, ...(ngDevMode ? [{ debugName: "displayCount" }] : /* istanbul ignore next */ []));
12722
+ this.popoverListItems = computed(() => {
12723
+ if (this.isHydratedStrategy()) {
12724
+ return this.allItems();
12725
+ }
12726
+ return this.resolvedPopoverItems();
12727
+ }, ...(ngDevMode ? [{ debugName: "popoverListItems" }] : /* istanbul ignore next */ []));
12728
+ //#endregion
12729
+ //#region ---- Constructor effects ----
12730
+ /** Avoid resetting lazy cache on every CD when `rowData` is a new object reference for the same row. */
12731
+ this.previousLazyRowId = undefined;
12732
+ effect(() => {
12733
+ const id = this.rowData?.['id'];
12734
+ const key = id != null && id !== '' ? String(id) : undefined;
12735
+ if (key === this.previousLazyRowId) {
12736
+ return;
12737
+ }
12738
+ this.previousLazyRowId = key;
12739
+ this.resetLazyState();
12740
+ });
12741
+ effect(() => {
12742
+ if (!this.isHydratedStrategy()) {
12743
+ const count = this.displayCount();
12744
+ void this.refreshSummaryLabel(count);
12745
+ }
12746
+ });
12747
+ effect(() => {
12748
+ const hydrated = this.isHydratedStrategy();
12749
+ const count = hydrated
12750
+ ? this.allItems().length
12751
+ : this.resolveStatus() === 'ready'
12752
+ ? this.resolvedPopoverItems().length
12753
+ : this.displayCount();
12754
+ void this.refreshPopoverHeader(count);
12755
+ });
12566
12756
  }
12567
12757
  //#endregion
12568
- //#region ---- Public Methods ----
12758
+ //#region ---- Public methods ----
12569
12759
  showMoreItems() {
12570
12760
  this.entityDetailPopoverService.hide();
12571
- this.openMorePopover();
12761
+ this.openPopoverFromRef(this.moreButton());
12572
12762
  }
12573
- onMorePopoverOpenChange(event) {
12574
- this.isMorePopoverOpen.set(event);
12763
+ openLazyPopover() {
12764
+ this.entityDetailPopoverService.hide();
12765
+ this.openPopoverFromRef(this.lazyTrigger());
12766
+ if (!this.isHydratedStrategy()) {
12767
+ void this.ensureLazyItemsLoaded();
12768
+ }
12769
+ }
12770
+ onPopoverOpenChange(event) {
12771
+ const open = this.coercePopoverOpenEvent(event);
12772
+ this.isMorePopoverOpen.set(open);
12773
+ if (open && !this.isHydratedStrategy()) {
12774
+ void this.ensureLazyItemsLoaded();
12775
+ }
12575
12776
  }
12576
12777
  async showItemDetail(item, index) {
12577
12778
  if (this.options['disableDetailPopover']) {
12578
12779
  return;
12579
12780
  }
12580
- const columnData = this.rowData[this.columnName];
12581
- const id = Array.isArray(columnData) ? columnData[index] : columnData;
12582
12781
  this.selectedItemIndex.set(index);
12583
12782
  this.closeMorePopover();
12584
- // Show entity detail popover using the service
12783
+ // Prefer the row's FK / stored value (columnName) first. List columns often use options.dataPath so
12784
+ // rawValue (and thus `item`) is only display text (e.g. manager.person.fullName) while managerId holds the real id.
12785
+ const columnData = this.rowData?.[this.columnName];
12786
+ let id;
12787
+ if (Array.isArray(columnData)) {
12788
+ const cell = columnData[index];
12789
+ id =
12790
+ typeof cell === 'object' && cell != null
12791
+ ? get(cell, this.valueField)
12792
+ : cell;
12793
+ }
12794
+ else if (!isNil(columnData) && columnData !== '') {
12795
+ id =
12796
+ typeof columnData === 'object' && columnData != null
12797
+ ? get(columnData, this.valueField)
12798
+ : columnData;
12799
+ }
12800
+ if (isNil(id) || id === '') {
12801
+ id = get(item, this.valueField);
12802
+ }
12803
+ if (isNil(id) || id === '') {
12804
+ return;
12805
+ }
12585
12806
  await this.entityDetailPopoverService.show(this.host, {
12586
12807
  entity: this.entity,
12587
- id: id,
12808
+ id,
12588
12809
  textField: this.textField,
12589
12810
  valueField: this.valueField,
12590
12811
  item,
@@ -12596,36 +12817,25 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12596
12817
  }
12597
12818
  const items = this.allItems();
12598
12819
  if (index < items.length) {
12599
- const item = items[index];
12600
- this.showItemDetail(item, index);
12820
+ this.showItemDetail(items[index], index);
12601
12821
  }
12602
12822
  }
12603
- //#endregion
12604
- //#region ---- Private Methods ----
12605
- openMorePopover() {
12606
- if (this.morePopover() && this.moreButton()) {
12607
- this.morePopover().target = this.moreButton().nativeElement;
12608
- this.morePopover().open();
12609
- this.isMorePopoverOpen.set(true);
12823
+ handleResolvedItemClick(index) {
12824
+ if (this.options['disableDetailPopover']) {
12825
+ return;
12610
12826
  }
12611
- }
12612
- closeMorePopover() {
12613
- if (this.morePopover()) {
12614
- this.morePopover().close();
12615
- this.isMorePopoverOpen.set(false);
12827
+ const items = this.resolvedPopoverItems();
12828
+ if (index < items.length) {
12829
+ this.showItemDetail(items[index], index);
12616
12830
  }
12617
12831
  }
12618
- extractItem(item) {
12619
- if (isNil(item)) {
12620
- return null;
12832
+ handlePopoverItemClick(index) {
12833
+ if (this.isHydratedStrategy()) {
12834
+ this.handleItemClick(index);
12621
12835
  }
12622
- if (typeof item === 'object') {
12623
- return item;
12836
+ else {
12837
+ this.handleResolvedItemClick(index);
12624
12838
  }
12625
- return {
12626
- [this.valueField]: item,
12627
- [this.textField]: item,
12628
- };
12629
12839
  }
12630
12840
  getDisplayRaw(item) {
12631
12841
  if (isNil(item)) {
@@ -12643,6 +12853,178 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12643
12853
  }
12644
12854
  return resolved ?? '';
12645
12855
  }
12856
+ //#endregion
12857
+ //#region ---- Private methods ----
12858
+ async refreshSummaryLabel(count) {
12859
+ const text = await this.translation.translateAsync('@general:widgets.lookup.column.items-count', {
12860
+ params: { count: String(count) },
12861
+ });
12862
+ this.summaryLabel.set(text);
12863
+ }
12864
+ async refreshPopoverHeader(count) {
12865
+ const text = await this.translation.translateAsync('@general:widgets.lookup.column.popover-title', {
12866
+ params: { count: String(count) },
12867
+ });
12868
+ this.popoverHeader.set(text);
12869
+ }
12870
+ resetLazyState() {
12871
+ this.resolvedPopoverItems.set([]);
12872
+ this.resolveStatus.set('idle');
12873
+ this.resolveError.set(null);
12874
+ }
12875
+ openPopoverFromRef(ref) {
12876
+ const popover = this.morePopover();
12877
+ if (popover && ref) {
12878
+ popover.target = ref.nativeElement;
12879
+ popover.open();
12880
+ this.isMorePopoverOpen.set(true);
12881
+ }
12882
+ }
12883
+ closeMorePopover() {
12884
+ const popover = this.morePopover();
12885
+ if (popover) {
12886
+ popover.close();
12887
+ this.isMorePopoverOpen.set(false);
12888
+ }
12889
+ }
12890
+ async ensureLazyItemsLoaded() {
12891
+ if (this.resolveStatus() === 'loading') {
12892
+ return;
12893
+ }
12894
+ const strategy = this.resolveStrategy();
12895
+ if (strategy === 'idsWithCount') {
12896
+ await this.loadByIds();
12897
+ return;
12898
+ }
12899
+ if (strategy === 'countOnly') {
12900
+ await this.loadByNamedQuery();
12901
+ }
12902
+ }
12903
+ /**
12904
+ * ax-popover may emit a boolean, CustomEvent with detail, or a DOM Event depending on version.
12905
+ */
12906
+ coercePopoverOpenEvent(event) {
12907
+ if (typeof event === 'boolean') {
12908
+ return event;
12909
+ }
12910
+ if (event && typeof event === 'object') {
12911
+ const o = event;
12912
+ if ('detail' in o) {
12913
+ return Boolean(o['detail']);
12914
+ }
12915
+ if ('open' in o) {
12916
+ return Boolean(o['open']);
12917
+ }
12918
+ }
12919
+ return false;
12920
+ }
12921
+ async loadByIds() {
12922
+ const ids = this.idsFromRow();
12923
+ if (!ids.length) {
12924
+ this.resolvedPopoverItems.set([]);
12925
+ this.resolveStatus.set('ready');
12926
+ return;
12927
+ }
12928
+ if (!this.entity?.includes('.')) {
12929
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
12930
+ this.resolveError.set(msg);
12931
+ this.resolveStatus.set('error');
12932
+ return;
12933
+ }
12934
+ this.resolveStatus.set('loading');
12935
+ this.resolveError.set(null);
12936
+ try {
12937
+ const accessor = this.entityService.withEntity(this.entity).data();
12938
+ const results = await Promise.all(ids.map((id) => accessor.byKey(id)));
12939
+ this.resolvedPopoverItems.set(results.filter((item) => item != null));
12940
+ this.resolveStatus.set('ready');
12941
+ }
12942
+ catch {
12943
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
12944
+ this.resolveError.set(msg);
12945
+ this.resolveStatus.set('error');
12946
+ this.resolvedPopoverItems.set([]);
12947
+ }
12948
+ }
12949
+ async loadByNamedQuery() {
12950
+ const cr = this.columnResolve();
12951
+ const key = cr?.queryKey;
12952
+ if (!key?.trim()) {
12953
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
12954
+ this.resolveError.set(msg);
12955
+ this.resolveStatus.set('error');
12956
+ return;
12957
+ }
12958
+ this.resolveStatus.set('loading');
12959
+ this.resolveError.set(null);
12960
+ try {
12961
+ const input = this.buildNamedQueryInput();
12962
+ const result = await this.queryExecutor.fetch(key, input);
12963
+ const items = this.extractItemsFromQueryResult(result);
12964
+ this.resolvedPopoverItems.set(items);
12965
+ this.resolveStatus.set('ready');
12966
+ }
12967
+ catch {
12968
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
12969
+ this.resolveError.set(msg);
12970
+ this.resolveStatus.set('error');
12971
+ this.resolvedPopoverItems.set([]);
12972
+ }
12973
+ }
12974
+ buildNamedQueryInput() {
12975
+ const map = this.parseQueryParamsMap();
12976
+ const row = this.rowData ?? {};
12977
+ if (!map) {
12978
+ return {};
12979
+ }
12980
+ const out = {};
12981
+ for (const [param, path] of Object.entries(map)) {
12982
+ out[param] = get(row, path);
12983
+ }
12984
+ return out;
12985
+ }
12986
+ parseQueryParamsMap() {
12987
+ const raw = this.columnResolve()?.queryParams;
12988
+ if (raw == null) {
12989
+ return null;
12990
+ }
12991
+ if (typeof raw === 'string') {
12992
+ try {
12993
+ const parsed = JSON.parse(raw);
12994
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
12995
+ ? parsed
12996
+ : null;
12997
+ }
12998
+ catch {
12999
+ return null;
13000
+ }
13001
+ }
13002
+ return raw;
13003
+ }
13004
+ extractItemsFromQueryResult(result) {
13005
+ if (result == null) {
13006
+ return [];
13007
+ }
13008
+ if (Array.isArray(result)) {
13009
+ return result;
13010
+ }
13011
+ const cr = this.columnResolve();
13012
+ const path = cr?.queryResultItemsPath ?? 'items';
13013
+ const nested = get(result, path);
13014
+ return Array.isArray(nested) ? nested : [];
13015
+ }
13016
+ extractItem(item) {
13017
+ if (isNil(item)) {
13018
+ return null;
13019
+ }
13020
+ if (typeof item === 'object') {
13021
+ return item;
13022
+ }
13023
+ return {
13024
+ [this.valueField]: item,
13025
+ [this.textField]: item,
13026
+ };
13027
+ }
12646
13028
  resolveDisplayValue(item) {
12647
13029
  if (isNil(item)) {
12648
13030
  return '';
@@ -12657,18 +13039,18 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12657
13039
  if (!isNil(rawDisplayValue)) {
12658
13040
  return this.resolveDisplayValue(rawDisplayValue);
12659
13041
  }
12660
- if (this.mlResolver.isValidMultiLanguageObject(item)) {
12661
- return this.mlResolver.resolve(item);
13042
+ if (this.translation.isValidMultiLanguageObject(item)) {
13043
+ return this.translation.resolve(item);
12662
13044
  }
12663
13045
  return '';
12664
13046
  }
12665
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
12666
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-flex ax-gap-1 ax-items-center\">\n <!-- Visible lookup chips (first N items) -->\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n <!-- Separator between chips -->\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n }\n <!-- No items -->\n @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n\n <!-- Overflow: open popover with full list -->\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-flex ax-items-center ax-end-0 ax-px-1 ax-cursor-pointer ax-h-full hover:ax-primary-lighter\"\n (click)=\"showMoreItems()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n\n<!-- Full list when user expands overflow -->\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onMorePopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[280px]\">\n <!-- Popover header -->\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">All {{ allItems().length }} Items</h3>\n </div>\n <!-- All items (scrollable) -->\n <div class=\"ax-max-h-64 ax-flex ax-flex-col ax-gap-3\">\n @for (item of allItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\" (click)=\"showItemDetail(item, $index)\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n</ax-popover>", dependencies: [{ kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
13047
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13048
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "repositionOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12667
13049
  }
12668
13050
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, decorators: [{
12669
13051
  type: Component,
12670
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXBadgeModule, AXButtonModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "<div class=\"ax-flex ax-gap-1 ax-items-center\">\n <!-- Visible lookup chips (first N items) -->\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n <!-- Separator between chips -->\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n }\n <!-- No items -->\n @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n\n <!-- Overflow: open popover with full list -->\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-flex ax-items-center ax-end-0 ax-px-1 ax-cursor-pointer ax-h-full hover:ax-primary-lighter\"\n (click)=\"showMoreItems()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n\n<!-- Full list when user expands overflow -->\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onMorePopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[280px]\">\n <!-- Popover header -->\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">All {{ allItems().length }} Items</h3>\n </div>\n <!-- All items (scrollable) -->\n <div class=\"ax-max-h-64 ax-flex ax-flex-col ax-gap-3\">\n @for (item of allItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\" (click)=\"showItemDetail(item, $index)\">\n {{ label }}\n </span>\n }\n </div>\n </div>\n</ax-popover>" }]
12671
- }], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
13052
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handleItemClick($index)\"\n >\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) {\n <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\"\n #moreButton\n >\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span\n class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\"\n #lazyTrigger\n >\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span\n [class.ax-cursor-pointer]=\"!options['disableDetailPopover']\"\n [class.hover:ax-text-primary]=\"!options['disableDetailPopover']\"\n [class.hover:ax-underline]=\"!options['disableDetailPopover']\"\n (click)=\"handlePopoverItemClick($index)\"\n >\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>\n" }]
13053
+ }], ctorParameters: () => [], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], lazyTrigger: [{ type: i0.ViewChild, args: ['lazyTrigger', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
12672
13054
 
12673
13055
  var lookupWidgetColumn_component = /*#__PURE__*/Object.freeze({
12674
13056
  __proto__: null,
@@ -12730,6 +13112,100 @@ const AXPLookupWidget = {
12730
13112
  },
12731
13113
  visible: true,
12732
13114
  },
13115
+ {
13116
+ name: 'columnResolveStrategy',
13117
+ title: 'Column resolve strategy',
13118
+ group: AXP_DATA_PROPERTY_GROUP,
13119
+ schema: {
13120
+ dataType: 'string',
13121
+ defaultValue: 'hydrated',
13122
+ interface: {
13123
+ name: 'columnResolveStrategy',
13124
+ path: 'options.columnResolve.strategy',
13125
+ type: AXPWidgetsCatalog.select,
13126
+ valueTransforms: objectKeyValueTransforms('id'),
13127
+ options: {
13128
+ dataSource: [
13129
+ { id: 'hydrated', title: 'Hydrated (titles on row)' },
13130
+ { id: 'idsWithCount', title: 'Ids + count on row (load titles on open)' },
13131
+ { id: 'countOnly', title: 'Count on row (named query on open)' },
13132
+ ],
13133
+ },
13134
+ },
13135
+ },
13136
+ visible: true,
13137
+ },
13138
+ {
13139
+ name: 'columnResolveCountFieldPath',
13140
+ title: 'Column resolve — count field path',
13141
+ group: AXP_DATA_PROPERTY_GROUP,
13142
+ schema: {
13143
+ dataType: 'string',
13144
+ interface: {
13145
+ name: 'columnResolveCountFieldPath',
13146
+ path: 'options.columnResolve.countFieldPath',
13147
+ type: AXPWidgetsCatalog.text,
13148
+ },
13149
+ },
13150
+ visible: true,
13151
+ },
13152
+ {
13153
+ name: 'columnResolveIdsPath',
13154
+ title: 'Column resolve — ids path',
13155
+ group: AXP_DATA_PROPERTY_GROUP,
13156
+ schema: {
13157
+ dataType: 'string',
13158
+ interface: {
13159
+ name: 'columnResolveIdsPath',
13160
+ path: 'options.columnResolve.idsPath',
13161
+ type: AXPWidgetsCatalog.text,
13162
+ },
13163
+ },
13164
+ visible: true,
13165
+ },
13166
+ {
13167
+ name: 'columnResolveQueryKey',
13168
+ title: 'Column resolve — query key (countOnly)',
13169
+ group: AXP_DATA_PROPERTY_GROUP,
13170
+ schema: {
13171
+ dataType: 'string',
13172
+ interface: {
13173
+ name: 'columnResolveQueryKey',
13174
+ path: 'options.columnResolve.queryKey',
13175
+ type: AXPWidgetsCatalog.text,
13176
+ },
13177
+ },
13178
+ visible: true,
13179
+ },
13180
+ {
13181
+ name: 'columnResolveQueryParams',
13182
+ title: 'Column resolve — query params (JSON: param → row path)',
13183
+ group: AXP_DATA_PROPERTY_GROUP,
13184
+ schema: {
13185
+ dataType: 'string',
13186
+ interface: {
13187
+ name: 'columnResolveQueryParams',
13188
+ path: 'options.columnResolve.queryParams',
13189
+ type: AXPWidgetsCatalog.text,
13190
+ },
13191
+ },
13192
+ visible: true,
13193
+ },
13194
+ {
13195
+ name: 'columnResolveQueryResultItemsPath',
13196
+ title: 'Column resolve — query result items path',
13197
+ group: AXP_DATA_PROPERTY_GROUP,
13198
+ schema: {
13199
+ dataType: 'string',
13200
+ defaultValue: 'items',
13201
+ interface: {
13202
+ name: 'columnResolveQueryResultItemsPath',
13203
+ path: 'options.columnResolve.queryResultItemsPath',
13204
+ type: AXPWidgetsCatalog.text,
13205
+ },
13206
+ },
13207
+ visible: true,
13208
+ },
12733
13209
  ],
12734
13210
  components: {
12735
13211
  view: {
@@ -12747,6 +13223,8 @@ const AXPLookupWidget = {
12747
13223
  },
12748
13224
  };
12749
13225
 
13226
+ //#endregion
13227
+
12750
13228
  /**
12751
13229
  * Source type
12752
13230
  */
@@ -15336,13 +15814,18 @@ class AXPDetailsViewBadgeStatusService {
15336
15814
  if (!entityDefinition) {
15337
15815
  return null;
15338
15816
  }
15339
- const statusPlugin = entityDefinition.plugins?.find((p) => p.name === 'status');
15340
- if (!statusPlugin?.options) {
15817
+ const statusPlugins = (entityDefinition.plugins ?? []).filter((p) => p.name === 'status');
15818
+ if (statusPlugins.length === 0) {
15341
15819
  return null;
15342
15820
  }
15343
- const statusOptions = statusPlugin.options;
15821
+ const explicitPrimary = statusPlugins.find((p) => p.options?.isPrimary === true);
15822
+ const primaryPlugin = explicitPrimary ??
15823
+ statusPlugins.find((p) => p.options?.isPrimary !== false) ??
15824
+ statusPlugins[0];
15825
+ const statusOptions = primaryPlugin.options;
15344
15826
  const statusField = statusOptions.field ?? 'statusId';
15345
15827
  const statusDefinitionKey = statusOptions.definition;
15828
+ const pluginReadonly = statusOptions.readonly === true;
15346
15829
  let definitionKey = typeof statusDefinitionKey === 'string' ? statusDefinitionKey : undefined;
15347
15830
  if (!definitionKey) {
15348
15831
  const statusProperty = entityDefinition.properties?.find((p) => p.name === statusField);
@@ -15363,7 +15846,7 @@ class AXPDetailsViewBadgeStatusService {
15363
15846
  definitionKey,
15364
15847
  value: String(value),
15365
15848
  dataPath: statusField,
15366
- readonly: currentPage?.isReadonly ?? false,
15849
+ readonly: pluginReadonly || (currentPage?.isReadonly ?? false),
15367
15850
  };
15368
15851
  }
15369
15852
  catch (error) {
@@ -15546,34 +16029,35 @@ class AXPBaseRelatedEntityConverter {
15546
16029
  groups,
15547
16030
  };
15548
16031
  }
15549
- async createGridLayoutStructure(singleInterface, helpers, evaluateExpressions, includeProperties) {
15550
- return await this.createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions, includeProperties);
16032
+ createGridLayoutStructure(singleInterface, helpers, includeProperties) {
16033
+ return this.createGridLayoutStructureInternal(singleInterface, helpers, includeProperties);
15551
16034
  }
15552
- //#region ---- Visible Evaluation Helpers ----
15553
- async getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions, includeProperties) {
16035
+ //#region ---- Property visibility (layout) ----
16036
+ /**
16037
+ * Chooses which properties appear in the related-entity grid layout.
16038
+ * Boolean `schema.visible === false` excludes a property. String templates are kept in the layout
16039
+ * and passed on `form-field.options.visible`; `AXPWidgetRendererDirective` uses
16040
+ * `AXPExpressionEvaluatorService` in `updateVisibility()` to evaluate them when context updates.
16041
+ */
16042
+ getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties) {
15554
16043
  let properties = helpers.getPropertyByGroupId(sectionId) ?? [];
15555
16044
  // Filter by includeProperties if provided
15556
16045
  if (includeProperties && includeProperties.length > 0) {
15557
16046
  const includeSet = new Set(includeProperties);
15558
16047
  properties = properties.filter((p) => includeSet.has(p.name));
15559
16048
  }
15560
- const evaluated = await Promise.all(properties.map(async (property) => {
15561
- let visible = property?.schema?.visible;
15562
- if (typeof visible === 'string' && evaluateExpressions) {
15563
- try {
15564
- const result = await evaluateExpressions({ visible });
15565
- visible = result.visible;
15566
- }
15567
- catch {
15568
- visible = true;
15569
- }
16049
+ const evaluated = properties.map((property) => {
16050
+ const visible = property?.schema?.visible;
16051
+ if (typeof visible === 'string') {
16052
+ // Include in layout; runtime evaluation via `AXPExpressionEvaluatorService` (widget renderer).
16053
+ return { property, visible: true };
15570
16054
  }
15571
16055
  return { property, visible: visible ?? true };
15572
- }));
16056
+ });
15573
16057
  return evaluated.filter((x) => x.visible !== false).map((x) => x.property);
15574
16058
  }
15575
- async createPropertyGrid(sectionId, helpers, evaluateExpressions, includeProperties) {
15576
- const visibleProperties = await this.getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions, includeProperties);
16059
+ createPropertyGrid(sectionId, helpers, includeProperties) {
16060
+ const visibleProperties = this.getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties);
15577
16061
  return {
15578
16062
  type: 'grid-layout',
15579
16063
  mode: 'edit',
@@ -15588,6 +16072,15 @@ class AXPBaseRelatedEntityConverter {
15588
16072
  },
15589
16073
  children: visibleProperties.map((p) => {
15590
16074
  const layout = helpers.getPropertyLayout(p.name);
16075
+ const sv = p.schema?.visible;
16076
+ let formFieldVisible;
16077
+ if (typeof sv === 'string') {
16078
+ // Evaluated by `AXPExpressionEvaluatorService` in `AXPWidgetRendererDirective.updateVisibility()`.
16079
+ formFieldVisible = sv;
16080
+ }
16081
+ else if (sv === false) {
16082
+ formFieldVisible = false;
16083
+ }
15591
16084
  return {
15592
16085
  type: 'grid-item-layout',
15593
16086
  name: p.name,
@@ -15602,6 +16095,8 @@ class AXPBaseRelatedEntityConverter {
15602
16095
  options: {
15603
16096
  label: p.title,
15604
16097
  showLabel: layout?.label?.visible ?? true,
16098
+ ...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
16099
+ ...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
15605
16100
  },
15606
16101
  children: [
15607
16102
  {
@@ -15621,13 +16116,15 @@ class AXPBaseRelatedEntityConverter {
15621
16116
  }),
15622
16117
  };
15623
16118
  }
15624
- async createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions, includeProperties) {
16119
+ createGridLayoutStructureInternal(singleInterface, helpers, includeProperties) {
15625
16120
  // Filter out empty sections (sections with no visible properties)
15626
- const sectionsWithProperties = await Promise.all((singleInterface?.sections ?? []).map(async (s) => {
15627
- const visibleProperties = await this.getVisiblePropertiesByGroupId(s.id, helpers, evaluateExpressions, includeProperties);
16121
+ const sectionsWithProperties = (singleInterface?.sections ?? []).map((s) => {
16122
+ const visibleProperties = this.getVisiblePropertiesByGroupId(s.id, helpers, includeProperties);
15628
16123
  return { section: s, hasProperties: visibleProperties.length > 0 };
15629
- }));
15630
- const validSections = sectionsWithProperties.filter((item) => item.hasProperties).map((item) => item.section);
16124
+ });
16125
+ const validSections = sectionsWithProperties
16126
+ .filter((item) => item.hasProperties)
16127
+ .map((item) => item.section);
15631
16128
  return {
15632
16129
  type: 'grid-layout',
15633
16130
  options: {
@@ -15639,7 +16136,7 @@ class AXPBaseRelatedEntityConverter {
15639
16136
  },
15640
16137
  },
15641
16138
  },
15642
- children: await Promise.all(validSections.map(async (s) => ({
16139
+ children: validSections.map((s) => ({
15643
16140
  type: 'grid-item-layout',
15644
16141
  name: s.id,
15645
16142
  options: {
@@ -15653,11 +16150,12 @@ class AXPBaseRelatedEntityConverter {
15653
16150
  options: {
15654
16151
  title: helpers.getGroupById(s.id)?.title ?? '',
15655
16152
  collapsible: true,
16153
+ showTitle: s.layout?.label?.visible ?? true,
15656
16154
  },
15657
- children: [await this.createPropertyGrid(s.id, helpers, evaluateExpressions, includeProperties)],
16155
+ children: [this.createPropertyGrid(s.id, helpers, includeProperties)],
15658
16156
  },
15659
16157
  ],
15660
- }))),
16158
+ })),
15661
16159
  };
15662
16160
  }
15663
16161
  }
@@ -15860,7 +16358,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
15860
16358
  execute: this.createExecuteFunction(entityDef, actions, evaluateExpressions, context),
15861
16359
  actions: await this.buildEvaluatedActions(actions, evaluateExpressions),
15862
16360
  // tabs: [...tabDetailTabs, ...tabListTabs],
15863
- content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers, evaluateExpressions, relatedEntity.properties)],
16361
+ content: [this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties)],
15864
16362
  };
15865
16363
  }
15866
16364
  //#region ---- Utility Methods ----
@@ -16252,6 +16750,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
16252
16750
  includeColumns,
16253
16751
  relatedTableColumns,
16254
16752
  customFilterDefinitions: relatedEntity.customFilterDefinitions,
16753
+ excludeProperties: relatedEntity.excludeProperties,
16255
16754
  },
16256
16755
  },
16257
16756
  ],
@@ -16310,7 +16809,7 @@ class AXPTabDetailsConverter extends AXPBaseRelatedEntityConverter {
16310
16809
  title: relatedEntity.title ?? entityDef?.title ?? '',
16311
16810
  icon: relatedEntity.icon || entityDef.icon,
16312
16811
  content: [
16313
- await this.createGridLayoutStructure(helpers.singleInterface, helpers, undefined, relatedEntity.properties),
16812
+ this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties),
16314
16813
  ],
16315
16814
  };
16316
16815
  }
@@ -16370,6 +16869,7 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
16370
16869
  includeColumns,
16371
16870
  relatedTableColumns,
16372
16871
  customFilterDefinitions: relatedEntity.customFilterDefinitions,
16872
+ excludeProperties: relatedEntity.excludeProperties,
16373
16873
  },
16374
16874
  },
16375
16875
  ],
@@ -16866,20 +17366,18 @@ class AXPMainEntityContentBuilder {
16866
17366
  const visibleInRelated = isFromRelated ? !!p.__layout : false;
16867
17367
  return visibleInMain || visibleInRelated;
16868
17368
  });
16869
- const visibilityEvaluated = await Promise.all(inView.map(async (p) => {
16870
- let visible = p.schema?.visible;
16871
- if (typeof visible === 'string' && evaluateExpressions) {
16872
- try {
16873
- const result = await evaluateExpressions({ visible });
16874
- visible = result.visible;
16875
- }
16876
- catch {
16877
- visible = true;
16878
- }
17369
+ // String `schema.visible` is not evaluated here (no `dependencies.expressionEvaluator` call).
17370
+ // The template is placed on `form-field.options.visible` below; at render time
17371
+ // `AXPWidgetRendererDirective` injects `AXPExpressionEvaluatorService` and evaluates it in
17372
+ // `updateVisibility()` (and on context changes via `hasVisibilityDependency`). Evaluating once
17373
+ // at build time would freeze visibility and break detail/edit when data changes.
17374
+ const visibilityEvaluated = inView.map((p) => {
17375
+ const v = p.schema?.visible;
17376
+ if (typeof v === 'string') {
17377
+ return { property: p, resolvedVisible: true };
16879
17378
  }
16880
- const resolved = visible !== false;
16881
- return { property: p, resolvedVisible: resolved };
16882
- }));
17379
+ return { property: p, resolvedVisible: v !== false };
17380
+ });
16883
17381
  const visibleProperties = visibilityEvaluated
16884
17382
  .filter((x) => x.resolvedVisible)
16885
17383
  .map((x) => x.property);
@@ -16903,6 +17401,7 @@ class AXPMainEntityContentBuilder {
16903
17401
  collapsible: true,
16904
17402
  isOpen: !s.collapsed,
16905
17403
  look: 'card',
17404
+ showTitle: s.layout?.label?.visible !== false,
16906
17405
  },
16907
17406
  children: [
16908
17407
  {
@@ -16927,8 +17426,9 @@ class AXPMainEntityContentBuilder {
16927
17426
  const hasOwnDisabled = p.schema.interface?.options?.disabled !== undefined;
16928
17427
  let formFieldVisible;
16929
17428
  const sv = p.schema?.visible;
17429
+ // Template strings are evaluated by `AXPExpressionEvaluatorService` in the widget renderer, not here.
16930
17430
  if (typeof sv === 'string') {
16931
- formFieldVisible = true;
17431
+ formFieldVisible = sv;
16932
17432
  }
16933
17433
  else if (sv === false) {
16934
17434
  formFieldVisible = false;
@@ -16945,6 +17445,7 @@ class AXPMainEntityContentBuilder {
16945
17445
  rowStart: layout?.positions?.lg?.rowStart,
16946
17446
  rowEnd: layout?.positions?.lg?.rowEnd,
16947
17447
  ...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
17448
+ ...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
16948
17449
  },
16949
17450
  children: [
16950
17451
  {