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

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 (59) 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 +137 -34
  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 +261 -58
  12. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-entity.mjs +1583 -632
  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-repeater-widget-column.component-BGQqY5Mw.mjs → acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs} +9 -4
  18. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-layout-widgets.mjs +1053 -409
  20. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-runtime.mjs +120 -9
  22. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  23. 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
  24. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs.map +1 -0
  25. 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
  26. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs.map +1 -0
  27. 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
  28. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-themes-default.mjs +10 -10
  30. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  31. package/fesm2022/{acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs → acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs} +2 -2
  32. package/fesm2022/acorex-platform-themes-shared-settings.provider-D13QB3Hr.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs +94 -0
  34. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs.map +1 -0
  35. 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
  36. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-themes-shared.mjs +183 -84
  38. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  39. package/fesm2022/acorex-platform-workflow.mjs +52 -11
  40. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  41. package/package.json +1 -1
  42. package/types/acorex-platform-common.d.ts +14 -10
  43. package/types/acorex-platform-core.d.ts +13 -2
  44. package/types/acorex-platform-domain.d.ts +28 -2
  45. package/types/acorex-platform-layout-builder.d.ts +61 -29
  46. package/types/acorex-platform-layout-designer.d.ts +88 -16
  47. package/types/acorex-platform-layout-entity.d.ts +190 -15
  48. package/types/acorex-platform-layout-widget-core.d.ts +81 -71
  49. package/types/acorex-platform-layout-widgets.d.ts +131 -54
  50. package/types/acorex-platform-runtime.d.ts +156 -61
  51. package/types/acorex-platform-workflow.d.ts +37 -2
  52. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +0 -1
  53. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +0 -1
  54. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +0 -1
  55. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +0 -1
  56. package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
  57. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +0 -65
  58. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +0 -1
  59. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +0 -1
@@ -2,24 +2,25 @@ import { AXToastService } from '@acorex/components/toast';
2
2
  import * as i6 from '@acorex/core/translation';
3
3
  import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
4
4
  import * as i4$4 from '@acorex/platform/common';
5
- import { AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
5
+ import { AXPEntityCommandScope, AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, 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';
11
- import { merge, get, castArray, cloneDeep, set, orderBy, omit, isNil, isEmpty, isEqual } from 'lodash-es';
10
+ import * as i3 from '@acorex/platform/layout/widget-core';
11
+ import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, createSelectProperty, createStringProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
12
+ import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
13
+ import { cloneDeep, merge, get, castArray, set, orderBy, omit, isNil, isEmpty, isEqual as isEqual$1 } from 'lodash-es';
14
+ import { transform, isEqual } from 'lodash';
12
15
  import { AXPSessionService, AXPAuthGuard, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
13
16
  import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
14
- import { defineCommand, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
17
+ import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
15
18
  import * as i1 from '@acorex/components/button';
16
19
  import { AXButtonModule } from '@acorex/components/button';
17
20
  import * as i4 from '@acorex/components/loading';
18
21
  import { AXLoadingModule } from '@acorex/components/loading';
19
22
  import * as i2 from '@acorex/components/popover';
20
23
  import { AXPopoverModule } from '@acorex/components/popover';
21
- import * as i3 from '@acorex/platform/layout/widget-core';
22
- import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, createSelectProperty, createStringProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
23
24
  import * as i5 from '@angular/common';
24
25
  import { CommonModule, AsyncPipe } from '@angular/common';
25
26
  import { AXPThemeLayoutBlockComponent, AXPPreloadFiltersComponent, AXPStateMessageComponent, AXPColumnItemListComponent, AXPDataSelectorService, AXPPageComponentRegistryService } from '@acorex/platform/layout/components';
@@ -69,7 +70,6 @@ import * as i5$3 from '@acorex/components/label';
69
70
  import { AXLabelModule } from '@acorex/components/label';
70
71
  import * as i7 from '@acorex/components/text-box';
71
72
  import { AXTextBoxModule } from '@acorex/components/text-box';
72
- import { transform, isEqual as isEqual$1 } from 'lodash';
73
73
 
74
74
  function ensureListActions(ctx) {
75
75
  ctx.interfaces.update((i) => {
@@ -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,405 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
781
803
  }]
782
804
  }] });
783
805
 
806
+ // #region Master
807
+ function entityMasterCreateAction() {
808
+ return {
809
+ title: '@general:actions.create.title',
810
+ command: {
811
+ name: 'Entity:Create',
812
+ },
813
+ priority: 'primary',
814
+ type: AXPSystemActionType.Create,
815
+ scope: AXPEntityCommandScope.TypeLevel,
816
+ };
817
+ }
818
+ function entityMasterEditAction() {
819
+ return {
820
+ title: '@general:actions.edit.title',
821
+ command: 'Entity:Update',
822
+ priority: 'secondary',
823
+ type: AXPSystemActionType.Update,
824
+ scope: AXPEntityCommandScope.Individual,
825
+ default: true,
826
+ };
827
+ }
828
+ function entityMasterBulkDeleteAction() {
829
+ return {
830
+ title: '@general:actions.delete-items.title',
831
+ command: 'delete-entity',
832
+ priority: 'primary',
833
+ type: AXPSystemActionType.Delete,
834
+ scope: AXPEntityCommandScope.Selected,
835
+ order: 100,
836
+ };
837
+ }
838
+ function entityMasterViewAction() {
839
+ return {
840
+ title: '@general:actions.view.title',
841
+ command: 'open-entity',
842
+ priority: 'secondary',
843
+ type: AXPSystemActionType.View,
844
+ scope: AXPEntityCommandScope.Individual,
845
+ default: true,
846
+ };
847
+ }
848
+ function entityMasterDeleteAction() {
849
+ return {
850
+ title: '@general:actions.delete.title',
851
+ command: 'delete-entity',
852
+ priority: 'secondary',
853
+ type: AXPSystemActionType.Delete,
854
+ scope: AXPEntityCommandScope.Individual,
855
+ order: 100,
856
+ };
857
+ }
858
+ function entityMasterCrudActions(options) {
859
+ const opts = {
860
+ create: true,
861
+ delete: true,
862
+ view: true,
863
+ edit: false,
864
+ ...options,
865
+ };
866
+ const actions = [];
867
+ if (opts.create) {
868
+ actions.push(entityMasterCreateAction());
869
+ }
870
+ if (opts.delete) {
871
+ actions.push(entityMasterBulkDeleteAction());
872
+ actions.push(entityMasterDeleteAction());
873
+ }
874
+ if (opts.view) {
875
+ actions.push(entityMasterViewAction());
876
+ }
877
+ if (opts.edit) {
878
+ actions.push(entityMasterEditAction());
879
+ }
880
+ return actions;
881
+ }
882
+ function entityMasterRecordActions() {
883
+ return [entityMasterDeleteAction()];
884
+ }
885
+ // #endregion
886
+ // #region Details
887
+ function entityDetailsCreateActions(parentId) {
888
+ return {
889
+ title: '@general:actions.create.title',
890
+ command: {
891
+ name: 'Entity:Create',
892
+ options: {
893
+ process: {
894
+ redirect: false,
895
+ canCreateNewOne: true,
896
+ data: {
897
+ [parentId]: '{{context.eval("id")}}',
898
+ },
899
+ },
900
+ },
901
+ },
902
+ priority: 'primary',
903
+ type: AXPSystemActionType.Create,
904
+ scope: AXPEntityCommandScope.TypeLevel,
905
+ };
906
+ }
907
+ /**
908
+ * Type-level Create with empty `process.data`; the FK is supplied from `relatedEntity.persistence.foreignKeyField`
909
+ * when the related list is built (see `mergeForeignKeyFieldIntoCreateActions`).
910
+ */
911
+ function entityDetailsCreateActionsDeferredParent() {
912
+ return {
913
+ title: '@general:actions.create.title',
914
+ command: {
915
+ name: 'Entity:Create',
916
+ options: {
917
+ process: {
918
+ redirect: false,
919
+ canCreateNewOne: true,
920
+ data: {},
921
+ },
922
+ },
923
+ },
924
+ priority: 'primary',
925
+ type: AXPSystemActionType.Create,
926
+ scope: AXPEntityCommandScope.TypeLevel,
927
+ };
928
+ }
929
+ /**
930
+ * Ensures each type-level `Entity:Create` action includes `process.data[foreignKeyField]` bound to the parent row id.
931
+ */
932
+ function mergeForeignKeyFieldIntoCreateActions(foreignKeyField, actions) {
933
+ if (!foreignKeyField || !actions?.length) {
934
+ return actions ?? [];
935
+ }
936
+ return actions.map((a) => {
937
+ const cmd = a.command;
938
+ if (typeof cmd !== 'object' ||
939
+ !cmd ||
940
+ cmd.name !== 'Entity:Create' ||
941
+ a.scope !== AXPEntityCommandScope.TypeLevel) {
942
+ return a;
943
+ }
944
+ const opts = (cmd.options ?? {});
945
+ const proc = (opts['process'] ?? {});
946
+ const data = { ...(proc['data'] ?? {}) };
947
+ if (data[foreignKeyField] === undefined || data[foreignKeyField] === null || data[foreignKeyField] === '') {
948
+ data[foreignKeyField] = '{{ context.eval("id") }}';
949
+ }
950
+ return {
951
+ ...a,
952
+ command: {
953
+ ...cmd,
954
+ options: { ...opts, process: { ...proc, data } },
955
+ },
956
+ };
957
+ });
958
+ }
959
+ /** Property names hidden on nested Create from a related list (`excludeProperties` plus `foreignKeyField` when set). */
960
+ function collectNestedCreateHiddenProperties(relatedEntity) {
961
+ const fk = relatedEntity.persistence?.foreignKeyField;
962
+ const merged = [...(relatedEntity.excludeProperties ?? [])];
963
+ if (fk) {
964
+ merged.push(fk);
965
+ }
966
+ const unique = [...new Set(merged)];
967
+ return unique.length ? unique : undefined;
968
+ }
969
+ function entityDetailsSimpleCondition(fk) {
970
+ return {
971
+ name: fk,
972
+ operator: { type: 'equal' },
973
+ value: '{{context.eval("id")}}',
974
+ };
975
+ }
976
+ function entityDetailsReferenceCondition(type) {
977
+ return [
978
+ {
979
+ name: 'reference.id',
980
+ operator: { type: 'equal' },
981
+ value: '{{context.eval("id")}}',
982
+ },
983
+ {
984
+ name: 'reference.type',
985
+ operator: { type: 'equal' },
986
+ value: type,
987
+ },
988
+ ];
989
+ }
990
+ function entityDetailsEditAction() {
991
+ return {
992
+ title: '@general:actions.edit.title',
993
+ // command: 'quick-modify-entity',
994
+ command: 'Entity:Update',
995
+ priority: 'secondary',
996
+ type: AXPSystemActionType.Update,
997
+ default: true,
998
+ scope: AXPEntityCommandScope.Individual,
999
+ };
1000
+ }
1001
+ function entityDetailsNewEditAction() {
1002
+ return {
1003
+ title: 'New Edit',
1004
+ command: {
1005
+ name: 'Entity:Update',
1006
+ },
1007
+ priority: 'secondary',
1008
+ type: AXPSystemActionType.Update,
1009
+ default: true,
1010
+ scope: AXPEntityCommandScope.Individual,
1011
+ };
1012
+ }
1013
+ function entityOverrideDetailsViewAction() {
1014
+ return {
1015
+ title: '@general:actions.view.title',
1016
+ command: 'open-entity',
1017
+ priority: 'secondary',
1018
+ hidden: true,
1019
+ type: AXPSystemActionType.View,
1020
+ scope: AXPEntityCommandScope.Individual,
1021
+ };
1022
+ }
1023
+ function entityDetailsCrudActions(parentId, options) {
1024
+ const opts = {
1025
+ create: true,
1026
+ delete: true,
1027
+ view: true,
1028
+ edit: true,
1029
+ ...options,
1030
+ };
1031
+ const actions = [];
1032
+ if (opts.create) {
1033
+ if (parentId) {
1034
+ actions.push(entityDetailsCreateActions(parentId));
1035
+ }
1036
+ else {
1037
+ actions.push(entityDetailsCreateActionsDeferredParent());
1038
+ }
1039
+ }
1040
+ if (opts.edit) {
1041
+ actions.push(entityDetailsEditAction());
1042
+ }
1043
+ if (opts.view) {
1044
+ actions.push(entityOverrideDetailsViewAction());
1045
+ }
1046
+ return actions;
1047
+ }
1048
+ function entityDetailsReferenceCreateActions(type) {
1049
+ return [
1050
+ {
1051
+ title: '@general:actions.create.title',
1052
+ command: {
1053
+ name: 'Entity:Create',
1054
+ options: {
1055
+ process: {
1056
+ redirect: false,
1057
+ canCreateNewOne: true,
1058
+ data: {
1059
+ reference: {
1060
+ id: '{{context.eval("id")}}',
1061
+ type: type,
1062
+ },
1063
+ },
1064
+ },
1065
+ },
1066
+ },
1067
+ priority: 'primary',
1068
+ type: AXPSystemActionType.Create,
1069
+ scope: AXPEntityCommandScope.TypeLevel,
1070
+ },
1071
+ entityDetailsEditAction(),
1072
+ entityOverrideDetailsViewAction(),
1073
+ ];
1074
+ }
1075
+ /**
1076
+ * Computes a diff between two plain objects with array-aware semantics.
1077
+ * - For arrays of objects with an id field, computes added/removed by id.
1078
+ * - For arrays of primitives or objects without id, uses deep equality.
1079
+ * - For scalars/objects, reports oldValue/newValue when changed.
1080
+ */
1081
+ function detectEntityChanges(oldObj, newObj) {
1082
+ return transform(newObj, (result, value, key) => {
1083
+ if (!isEqual(value, oldObj[key])) {
1084
+ const oldValue = oldObj[key];
1085
+ if (Array.isArray(value) || Array.isArray(oldValue)) {
1086
+ const oldArray = Array.isArray(oldValue) ? oldValue : [];
1087
+ const newArray = Array.isArray(value) ? value : [];
1088
+ const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
1089
+ if (hasId) {
1090
+ const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
1091
+ const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
1092
+ result[key] = { oldValue, newValue: value, added, removed };
1093
+ }
1094
+ else {
1095
+ const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual(item, oldItem)));
1096
+ const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual(item, newItem)));
1097
+ result[key] = { oldValue, newValue: value, added, removed };
1098
+ }
1099
+ }
1100
+ else {
1101
+ result[key] = { oldValue, newValue: value };
1102
+ }
1103
+ }
1104
+ }, {});
1105
+ }
1106
+ //#endregion
1107
+
1108
+ /**
1109
+ * Maps entity property `description` (or any i18n key / multi-language payload) to `form-field`
1110
+ * widget options. Returns `undefined` when there is nothing to show.
1111
+ */
1112
+ function hintFormFieldOptionsFromDescription(description) {
1113
+ if (description == null) {
1114
+ return undefined;
1115
+ }
1116
+ if (typeof description === 'string' && description.trim() === '') {
1117
+ return undefined;
1118
+ }
1119
+ return { hint: description, hintDisplayMode: 'icon' };
1120
+ }
1121
+ //#endregion
1122
+
1123
+ //#endregion
1124
+ //#region ---- Helpers ----
1125
+ function isRelatedEntityStringColumns(columns) {
1126
+ return columns.length === 0 || typeof columns[0] === 'string';
1127
+ }
1128
+ /**
1129
+ * Property names from `AXPRelatedEntity.columns` (no expression evaluation).
1130
+ * Empty array means caller can treat as “no name filter” (show all allowed by entity).
1131
+ */
1132
+ function getRelatedEntityColumnNames(columns) {
1133
+ if (!columns?.length) {
1134
+ return [];
1135
+ }
1136
+ if (isRelatedEntityStringColumns(columns)) {
1137
+ return [...columns];
1138
+ }
1139
+ return columns.map((c) => c.name);
1140
+ }
1141
+ function isTruthyVisible(value) {
1142
+ if (value === true) {
1143
+ return true;
1144
+ }
1145
+ if (value === false || value === null || value === undefined) {
1146
+ return false;
1147
+ }
1148
+ if (typeof value === 'string') {
1149
+ const t = value.trim().toLowerCase();
1150
+ return t !== '' && t !== 'false' && t !== '0';
1151
+ }
1152
+ if (typeof value === 'number') {
1153
+ return value !== 0 && !Number.isNaN(value);
1154
+ }
1155
+ return Boolean(value);
1156
+ }
1157
+ //#endregion
1158
+ //#region ---- Public API ----
1159
+ /**
1160
+ * Resolves `AXPRelatedEntity.columns` for `entity-list`:
1161
+ * - `string[]` → `includeColumns` only (legacy).
1162
+ * - `AXPEntityTableColumn[]` → evaluates `options.visible` when it is a string (parent detail context),
1163
+ * drops hidden / invisible columns, and returns `relatedTableColumns` + matching `includeColumns`.
1164
+ */
1165
+ async function resolveRelatedEntityColumns(columns, expressionEvaluator, scope) {
1166
+ if (!columns?.length) {
1167
+ return {};
1168
+ }
1169
+ if (isRelatedEntityStringColumns(columns)) {
1170
+ return { includeColumns: getRelatedEntityColumnNames(columns) };
1171
+ }
1172
+ const relatedTableColumns = [];
1173
+ for (const raw of columns) {
1174
+ const col = cloneDeep(raw);
1175
+ if (col.hidden === true) {
1176
+ continue;
1177
+ }
1178
+ let visible = col.options?.visible;
1179
+ if (visible === undefined) {
1180
+ visible = true;
1181
+ }
1182
+ else if (typeof visible === 'string') {
1183
+ const evaluated = await expressionEvaluator.evaluate(visible, scope);
1184
+ visible = isTruthyVisible(evaluated);
1185
+ }
1186
+ if (visible === false) {
1187
+ continue;
1188
+ }
1189
+ col.options = { ...col.options, visible: true };
1190
+ relatedTableColumns.push(col);
1191
+ }
1192
+ return {
1193
+ relatedTableColumns,
1194
+ includeColumns: relatedTableColumns.map((c) => c.name),
1195
+ };
1196
+ }
1197
+ //#endregion
1198
+
1199
+ /** Registered widget name for entity create/update step wizard (dialog footer uses `widget:${name}.next`). */
1200
+ const ENTITY_FORM_STEP_WIZARD_NAME = 'entityFormStepWizard';
1201
+ /** Footer command: validate, persist main entity, merge context, advance wizard. */
1202
+ const ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE = 'entity-form-first-step-continue';
1203
+ /** Footer command: close dialog (data already saved per step). */
1204
+ const ENTITY_FORM_ACTION_DONE = 'entity-form-done';
784
1205
  //#endregion
785
1206
  class AXPEntityFormBuilderService {
786
1207
  constructor() {
@@ -788,11 +1209,12 @@ class AXPEntityFormBuilderService {
788
1209
  this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
789
1210
  this.layoutBuilder = inject(AXPLayoutBuilderService);
790
1211
  this.deviceService = inject(AXPDeviceService);
1212
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
791
1213
  }
792
1214
  //#endregion
793
1215
  //#region ---- Public API ----
794
1216
  entity(fullName) {
795
- return new InterfaceSelector(this.entityRegistry, this.layoutBuilder, this.deviceService, fullName, this);
1217
+ return new InterfaceSelector(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, fullName, this);
796
1218
  }
797
1219
  /**
798
1220
  * Fetches a record by ID for the specified entity.
@@ -826,15 +1248,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
826
1248
  }] });
827
1249
  //#region ---- Builders ----
828
1250
  class InterfaceSelector {
829
- constructor(entityRegistry, layoutBuilder, deviceService, fullName, formBuilderService) {
1251
+ constructor(entityRegistry, layoutBuilder, deviceService, expressionEvaluator, fullName, formBuilderService) {
830
1252
  this.entityRegistry = entityRegistry;
831
1253
  this.layoutBuilder = layoutBuilder;
832
1254
  this.deviceService = deviceService;
1255
+ this.expressionEvaluator = expressionEvaluator;
833
1256
  this.fullName = fullName;
834
1257
  this.formBuilderService = formBuilderService;
835
1258
  }
836
1259
  create(initialData) {
837
- const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'create', undefined, this.formBuilderService);
1260
+ const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'create', undefined, this.formBuilderService);
838
1261
  if (initialData) {
839
1262
  filter.context(initialData);
840
1263
  }
@@ -850,7 +1273,7 @@ class InterfaceSelector {
850
1273
  initialData = data;
851
1274
  recordId = data['id'] || data['_id'];
852
1275
  }
853
- const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'update', recordId, this.formBuilderService);
1276
+ const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'update', recordId, this.formBuilderService);
854
1277
  if (Object.keys(initialData).length > 0) {
855
1278
  filter.context(initialData);
856
1279
  }
@@ -866,7 +1289,7 @@ class InterfaceSelector {
866
1289
  initialData = data;
867
1290
  recordId = data['id'] || data['_id'];
868
1291
  }
869
- const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.fullName, 'single', recordId, this.formBuilderService);
1292
+ const filter = new PropertyFilter(this.entityRegistry, this.layoutBuilder, this.deviceService, this.expressionEvaluator, this.fullName, 'single', recordId, this.formBuilderService);
870
1293
  if (Object.keys(initialData).length > 0) {
871
1294
  filter.context(initialData);
872
1295
  }
@@ -874,10 +1297,11 @@ class InterfaceSelector {
874
1297
  }
875
1298
  }
876
1299
  class PropertyFilter {
877
- constructor(entityRegistry, layoutBuilder, deviceService, fullName, kind, recordId, formBuilderService) {
1300
+ constructor(entityRegistry, layoutBuilder, deviceService, expressionEvaluator, fullName, kind, recordId, formBuilderService) {
878
1301
  this.entityRegistry = entityRegistry;
879
1302
  this.layoutBuilder = layoutBuilder;
880
1303
  this.deviceService = deviceService;
1304
+ this.expressionEvaluator = expressionEvaluator;
881
1305
  this.fullName = fullName;
882
1306
  this.kind = kind;
883
1307
  this.formBuilderService = formBuilderService;
@@ -934,11 +1358,10 @@ class PropertyFilter {
934
1358
  return this;
935
1359
  }
936
1360
  async build() {
937
- const dialog = await this.buildDialog();
1361
+ const dialog = await this.buildDialog({});
938
1362
  return dialog.build();
939
1363
  }
940
1364
  async show() {
941
- const dialog = await this.buildDialog();
942
1365
  // Context: always load by key when we have a record id (authoritative full row), then merge any
943
1366
  // caller/list row context. Skipping fetch when initialContext was non-empty left dialogs without
944
1367
  // fields not present on the passed row (e.g. Entity:View single() with partial list payload).
@@ -963,14 +1386,17 @@ class PropertyFilter {
963
1386
  }
964
1387
  }
965
1388
  const effectiveContext = merge({}, this.initialContext, baseContext);
1389
+ const dialog = await this.buildDialog(effectiveContext);
966
1390
  dialog.setContext(effectiveContext);
967
1391
  return await dialog.show();
968
1392
  }
969
1393
  /**
970
1394
  * Builds the dialog node structure without showing it.
971
1395
  * This method is shared by both build() and show() methods.
1396
+ *
1397
+ * @param modelForRelatedEval Merged root model used to evaluate related list filters (show() passes full row).
972
1398
  */
973
- async buildDialog() {
1399
+ async buildDialog(modelForRelatedEval) {
974
1400
  const { moduleName, entityName } = parseEntityFullName(this.fullName);
975
1401
  const entity = await this.entityRegistry.resolve(moduleName, entityName);
976
1402
  // Select the appropriate interface based on kind
@@ -988,7 +1414,9 @@ class PropertyFilter {
988
1414
  // Collect all groups for title lookup (main + merged)
989
1415
  const allGroups = [...(entity.groups ?? [])];
990
1416
  // Process merge-detail related entities
991
- const mergeDetailEntities = (entity.relatedEntities ?? []).filter((re) => !re.hidden && re.layout?.type === 'merge-detail');
1417
+ const mergeDetailEntities = (entity.relatedEntities ?? []).filter((re) => !re.hidden &&
1418
+ re.layout?.type === 'merge-detail' &&
1419
+ isRelatedEntityIncludedOnMasterForm(re, this.kind, 'merge-detail'));
992
1420
  // Build merged properties map by section
993
1421
  const mergedPropsBySection = new Map();
994
1422
  // Initialize with main entity properties
@@ -1161,104 +1589,271 @@ class PropertyFilter {
1161
1589
  return sectionProps.length > 0 || extraFields.length > 0;
1162
1590
  });
1163
1591
  const sectionScaleFactors = normalizeLayoutFillRowGaps(renderedSections, (s) => s.layout, (s) => s.id);
1592
+ const filterEvalRoot = merge({}, this.initialContext, modelForRelatedEval);
1593
+ const listRelatedEntities = collectListRelatedEntitiesForFormWizard(entity, this.kind);
1594
+ const useListWizard = listRelatedEntities.length > 0 && (this.kind === 'create' || this.kind === 'update');
1595
+ const listWizardSteps = [];
1596
+ for (const re of listRelatedEntities) {
1597
+ const [rm, en] = re.entity.split('.');
1598
+ const relDef = await this.entityRegistry.resolve(rm, en);
1599
+ listWizardSteps.push({
1600
+ stepId: buildEntityFormRelatedStepId(re),
1601
+ title: (re.title ?? relDef?.title ?? re.entity),
1602
+ rel: re,
1603
+ });
1604
+ }
1605
+ const prebuiltRelatedListConfigs = useListWizard
1606
+ ? await Promise.all(listWizardSteps.map((s) => this.buildRelatedEntityListWidgetOptions(s.rel, filterEvalRoot)))
1607
+ : [];
1608
+ const mainFormGridArgs = {
1609
+ entity,
1610
+ finalSections,
1611
+ mergedPropsBySection,
1612
+ allGroups,
1613
+ extraFieldsByGroup: this.extraFieldsByGroup,
1614
+ sectionScaleFactors,
1615
+ };
1164
1616
  const dialog = this.layoutBuilder.create().dialog((d) => {
1165
1617
  d.setTitle(title);
1166
- d.setSize(this.externalSize ?? calculatedSize);
1618
+ d.setSize(this.externalSize ?? (useListWizard ? 'lg' : calculatedSize));
1167
1619
  d.setCloseButton(true);
1168
1620
  d.content((layout) => {
1169
- // Default mode: 'view' for single, 'edit' for create/update
1170
1621
  const defaultMode = this.kind === 'single' ? 'view' : 'edit';
1171
1622
  layout.mode(this.externalMode ?? defaultMode);
1172
- // Wrap content in a grid container to support colSpan layouts
1173
- layout.grid((grid) => {
1174
- // Configure grid with 12 columns (like Bootstrap)
1175
- grid.setColumns(12);
1176
- grid.setGap('1rem');
1177
- for (const section of finalSections) {
1178
- const groupId = section.id;
1179
- const sectionProps = mergedPropsBySection.get(groupId) ?? [];
1180
- const extraFields = this.extraFieldsByGroup.get(groupId) ?? [];
1181
- if (sectionProps.length === 0 && extraFields.length === 0) {
1182
- continue;
1183
- }
1184
- const sectionLayout = section.layout ?? undefined;
1185
- grid.item(sectionLayout, (fs) => {
1186
- const sectionVisible = section.layout?.visible;
1187
- if (sectionVisible !== undefined && sectionVisible !== null) {
1188
- fs.visible(sectionVisible);
1189
- }
1190
- fs.setLook('fieldset');
1191
- fs.setTitle((getGroupTitleFromList(allGroups, groupId) || groupId));
1192
- fs.setCols(12);
1193
- // Sort properties by order within section
1194
- const orderedProps = [...sectionProps].sort((a, b) => {
1195
- const aOrder = a.__order ?? Infinity;
1196
- const bOrder = b.__order ?? Infinity;
1197
- return aOrder - bOrder;
1623
+ if (useListWizard) {
1624
+ layout.stepWizard((w) => {
1625
+ w.name(ENTITY_FORM_STEP_WIZARD_NAME);
1626
+ w.setLook('circular');
1627
+ w.setShowActions(false);
1628
+ const mainStepTitle = (entity.formats?.individual ||
1629
+ entity.title ||
1630
+ `${entity.module}.${entity.name}`);
1631
+ w.step('main', mainStepTitle, (step) => {
1632
+ step.content((inner) => {
1633
+ inner.grid((grid) => this.fillMainEntityFormGrid(grid, mainFormGridArgs));
1198
1634
  });
1199
- applySectionScaleToProperties(orderedProps, sectionScaleFactors.get(groupId));
1200
- normalizeLayoutFillRowGaps(orderedProps, (p) => p.__layout);
1201
- for (const prop of orderedProps) {
1202
- const mergedProp = prop;
1203
- const dataPath = mergedProp.__dataPath;
1204
- const fieldPath = dataPath ? `${dataPath}.${prop.name}` : prop.name;
1205
- fs.formField(prop.title, (field) => {
1206
- field.path(fieldPath);
1207
- if (prop.schema?.visible !== undefined) {
1208
- field.visible(prop.schema?.visible);
1209
- }
1210
- if (prop.schema?.readonly !== undefined) {
1211
- field.readonly(prop.schema.readonly);
1212
- }
1213
- if (prop.schema?.defaultValue !== undefined) {
1214
- field.defaultValue(prop.schema.defaultValue);
1215
- }
1216
- const fieldLayout = toFieldLayout(mergedProp.__layout);
1217
- if (fieldLayout) {
1218
- field.layout(fieldLayout);
1219
- }
1220
- if (fieldLayout?.label?.visible === false) {
1221
- field.setShowLabel(false);
1222
- }
1223
- const widgetType = prop.schema?.interface?.type || '';
1224
- const widgetOptions = buildWidgetOptions(prop);
1225
- const extendedProperties = buildWidgetExtendedProperties(prop);
1226
- const finalExtendedProps = dataPath
1227
- ? prefixExpressions(extendedProperties, dataPath)
1228
- : extendedProperties;
1229
- field.customWidget(widgetType, { ...widgetOptions, ...finalExtendedProps });
1230
- });
1231
- }
1232
- for (const extra of extraFields) {
1233
- const label = extra.path?.split('.').slice(-1)[0] || extra.path;
1234
- fs.formField(label, (f) => {
1235
- const legacy = toCompatFormFieldBuilder(f);
1236
- legacy.path(extra.path);
1237
- extra.delegate?.(legacy);
1635
+ });
1636
+ listWizardSteps.forEach((step, idx) => {
1637
+ const built = prebuiltRelatedListConfigs[idx];
1638
+ w.step(step.stepId, step.title, (st) => {
1639
+ st.content((inner) => {
1640
+ // Step content uses root LayoutBuilder (no customWidget); wrap in flex like other roots.
1641
+ inner.flex((flex) => {
1642
+ flex.customWidget(AXPWidgetsCatalog.entityList, (iw) => {
1643
+ iw.name(built.name);
1644
+ if (built.defaultValue) {
1645
+ iw.defaultValue(built.defaultValue);
1646
+ }
1647
+ iw.options(built.options);
1648
+ });
1649
+ });
1238
1650
  });
1239
- }
1651
+ });
1240
1652
  });
1241
- }
1242
- });
1653
+ });
1654
+ }
1655
+ else {
1656
+ layout.grid((grid) => this.fillMainEntityFormGrid(grid, mainFormGridArgs));
1657
+ }
1243
1658
  });
1244
- // Actions: external if provided, otherwise default based on kind
1245
- if (this.externalActionsDelegate) {
1659
+ if (this.externalActionsDelegate && !useListWizard) {
1246
1660
  d.setActions(this.externalActionsDelegate);
1247
1661
  }
1662
+ else if (useListWizard) {
1663
+ d.setActions((ab) => configureEntityFormWizardFooterActions(ab, ENTITY_FORM_STEP_WIZARD_NAME, this.kind === 'create'
1664
+ ? '{{ ((typeof context.id === "string" && context.id.length > 0) || (typeof context.id === "number" && !isNaN(context.id)) || (typeof context._id === "string" && context._id.length > 0) || (typeof context._id === "number" && !isNaN(context._id))) ? "@general:entity-form.update-and-continue.title" : "@general:entity-form.create-and-continue.title" }}'
1665
+ : '@general:entity-form.save-and-continue.title'));
1666
+ }
1248
1667
  else if (this.kind === 'single') {
1249
- // For single (read-only view), only show cancel/close button
1250
- d.setActions((a) => a.cancel());
1668
+ d.setActions((a) => a.submit('@general:actions.close.title'));
1251
1669
  }
1252
1670
  else {
1253
- // For create/update, show cancel + submit
1254
- d.setActions((a) => a.cancel().submit());
1671
+ d.setActions((a) => a.submit(this.kind === 'create' ? '@general:actions.create.title' : '@general:actions.apply.title'));
1672
+ }
1673
+ if (useListWizard && this.onActionHandler) {
1674
+ d.onAction(this.createWizardOnActionWrapper(this.onActionHandler));
1255
1675
  }
1256
- if (this.onActionHandler) {
1676
+ else if (this.onActionHandler) {
1257
1677
  d.onAction(this.onActionHandler);
1258
1678
  }
1679
+ else if (this.kind === 'single') {
1680
+ d.onAction(async () => ({ success: true, skipValidate: true }));
1681
+ }
1259
1682
  });
1260
1683
  return dialog;
1261
1684
  }
1685
+ /**
1686
+ * Renders main entity fieldsets and fields into a grid container (step 1 or non-wizard form).
1687
+ */
1688
+ fillMainEntityFormGrid(grid, args) {
1689
+ grid.setColumns(12);
1690
+ grid.setGap('1rem');
1691
+ for (const section of args.finalSections) {
1692
+ const groupId = section.id;
1693
+ const sectionProps = args.mergedPropsBySection.get(groupId) ?? [];
1694
+ const extraFields = args.extraFieldsByGroup.get(groupId) ?? [];
1695
+ if (sectionProps.length === 0 && extraFields.length === 0) {
1696
+ continue;
1697
+ }
1698
+ const sectionLayout = section.layout ?? undefined;
1699
+ grid.item(sectionLayout, (fs) => {
1700
+ const sectionVisible = section.layout?.visible;
1701
+ if (sectionVisible !== undefined && sectionVisible !== null) {
1702
+ fs.visible(sectionVisible);
1703
+ }
1704
+ fs.setLook('fieldset');
1705
+ fs.setTitle((getGroupTitleFromList(args.allGroups, groupId) || groupId));
1706
+ fs.setCols(12);
1707
+ const sectionLabelVisible = section.layout?.label?.visible ??
1708
+ args.entity.interfaces?.master?.single?.sections?.find((s) => s?.id === groupId)?.layout?.label?.visible;
1709
+ if (sectionLabelVisible === false) {
1710
+ const legendHidden = { showTitle: false, showHeader: false };
1711
+ fs.setOptions(legendHidden);
1712
+ }
1713
+ const orderedProps = [...sectionProps].sort((a, b) => {
1714
+ const aOrder = a.__order ?? Infinity;
1715
+ const bOrder = b.__order ?? Infinity;
1716
+ return aOrder - bOrder;
1717
+ });
1718
+ applySectionScaleToProperties(orderedProps, args.sectionScaleFactors.get(groupId));
1719
+ normalizeLayoutFillRowGaps(orderedProps, (p) => p.__layout);
1720
+ for (const prop of orderedProps) {
1721
+ const mergedProp = prop;
1722
+ const dataPath = mergedProp.__dataPath;
1723
+ const fieldPath = dataPath ? `${dataPath}.${prop.name}` : prop.name;
1724
+ fs.formField(prop.title, (field) => {
1725
+ field.path(fieldPath);
1726
+ if (prop.schema?.visible !== undefined) {
1727
+ field.visible(prop.schema?.visible);
1728
+ }
1729
+ if (prop.schema?.readonly !== undefined) {
1730
+ field.readonly(prop.schema.readonly);
1731
+ }
1732
+ if (prop.schema?.defaultValue !== undefined) {
1733
+ field.defaultValue(prop.schema.defaultValue);
1734
+ }
1735
+ const fieldLayout = toFieldLayout(mergedProp.__layout);
1736
+ if (fieldLayout) {
1737
+ field.layout(fieldLayout);
1738
+ }
1739
+ if (fieldLayout?.label?.visible === false) {
1740
+ field.setShowLabel(false);
1741
+ }
1742
+ const hintOpts = hintFormFieldOptionsFromDescription(prop.description);
1743
+ if (hintOpts) {
1744
+ field.setOptions(hintOpts);
1745
+ }
1746
+ const widgetType = prop.schema?.interface?.type || '';
1747
+ const widgetOptions = buildWidgetOptions(prop);
1748
+ const extendedProperties = buildWidgetExtendedProperties(prop);
1749
+ const finalExtendedProps = dataPath ? prefixExpressions(extendedProperties, dataPath) : extendedProperties;
1750
+ field.customWidget(widgetType, { ...widgetOptions, ...finalExtendedProps });
1751
+ });
1752
+ }
1753
+ for (const extra of extraFields) {
1754
+ const label = extra.path?.split('.').slice(-1)[0] || extra.path;
1755
+ fs.formField(label, (f) => {
1756
+ const legacy = toCompatFormFieldBuilder(f);
1757
+ legacy.path(extra.path);
1758
+ extra.delegate?.(legacy);
1759
+ });
1760
+ }
1761
+ });
1762
+ }
1763
+ }
1764
+ async buildRelatedEntityListWidgetOptions(relatedEntity, filterEvalRoot) {
1765
+ const evaluateExpressions = async (actionData) => {
1766
+ const scope = {
1767
+ context: {
1768
+ eval: (path) => get(filterEvalRoot, path),
1769
+ },
1770
+ };
1771
+ return await this.expressionEvaluator.evaluate(actionData, scope);
1772
+ };
1773
+ const syncRelatedFromRoot = this.kind === 'create';
1774
+ const foreignKeyField = relatedEntity.persistence?.foreignKeyField;
1775
+ const filters = syncRelatedFromRoot
1776
+ ? []
1777
+ : await Promise.all(relatedEntity.conditions?.map(async (c) => {
1778
+ const value = await evaluateExpressions(c.value);
1779
+ return {
1780
+ field: c.name,
1781
+ operator: c.operator,
1782
+ value,
1783
+ hidden: true,
1784
+ };
1785
+ }) ?? []);
1786
+ const rawOrEvaluatedActions = syncRelatedFromRoot
1787
+ ? (relatedEntity.actions ?? [])
1788
+ : await this.evaluateRelatedEntityActionsForFormWizard(relatedEntity.actions, filterEvalRoot);
1789
+ const evaluatedActions = mergeForeignKeyFieldIntoCreateActions(foreignKeyField, rawOrEvaluatedActions);
1790
+ const columnScope = {
1791
+ context: {
1792
+ eval: (path) => get(filterEvalRoot, path),
1793
+ },
1794
+ };
1795
+ const { includeColumns, relatedTableColumns } = await resolveRelatedEntityColumns(relatedEntity.columns, this.expressionEvaluator, columnScope);
1796
+ const nestedCreateHiddenProperties = collectNestedCreateHiddenProperties(relatedEntity);
1797
+ return {
1798
+ name: `${relatedEntity.entity}-entity-form-step`,
1799
+ defaultValue: {
1800
+ toolbar: {
1801
+ filters,
1802
+ },
1803
+ },
1804
+ options: {
1805
+ entity: relatedEntity.entity,
1806
+ showEntityActions: true,
1807
+ showToolbar: false,
1808
+ actions: evaluatedActions,
1809
+ maxHeight: '400px',
1810
+ includeColumns,
1811
+ relatedTableColumns,
1812
+ customFilterDefinitions: relatedEntity.customFilterDefinitions,
1813
+ ...(foreignKeyField ? { foreignKeyField } : {}),
1814
+ ...(nestedCreateHiddenProperties?.length ? { nestedCreateHiddenProperties } : {}),
1815
+ ...(syncRelatedFromRoot && relatedEntity.conditions?.length
1816
+ ? {
1817
+ relatedFilterConditionSpecs: cloneDeep(relatedEntity.conditions),
1818
+ syncRelatedListFiltersFromDialogContext: true,
1819
+ }
1820
+ : {}),
1821
+ },
1822
+ };
1823
+ }
1824
+ async evaluateRelatedEntityActionsForFormWizard(actions, filterEvalRoot) {
1825
+ const list = actions ?? [];
1826
+ const scope = {
1827
+ context: {
1828
+ eval: (path) => get(filterEvalRoot, path),
1829
+ },
1830
+ };
1831
+ return Promise.all(list.map(async (action) => {
1832
+ if (action.scope === AXPEntityCommandScope.Individual) {
1833
+ return action;
1834
+ }
1835
+ return (await this.expressionEvaluator.evaluate(action, scope));
1836
+ }));
1837
+ }
1838
+ createWizardOnActionWrapper(userHandler) {
1839
+ return async (ref) => {
1840
+ const action = ref.action();
1841
+ if (action === ENTITY_FORM_ACTION_DONE) {
1842
+ return { success: true, data: ref.context(), skipValidate: true };
1843
+ }
1844
+ if (action === ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE) {
1845
+ const out = (await userHandler(ref));
1846
+ if (out?.success === false) {
1847
+ return out;
1848
+ }
1849
+ const item = out?.data?.item ?? out?.data ?? {};
1850
+ ref.patchContext?.(merge({}, ref.context(), item));
1851
+ await ref.invokeWidget?.(ENTITY_FORM_STEP_WIZARD_NAME, 'next', { setLoading: ref.setLoading });
1852
+ return merge({}, out, { keepDialogOpen: true });
1853
+ }
1854
+ return await userHandler(ref);
1855
+ };
1856
+ }
1262
1857
  computeAllowedNames(allNames) {
1263
1858
  if (this.includeList && this.includeList.size > 0) {
1264
1859
  return new Set(allNames.filter((n) => this.includeList.has(n)));
@@ -1539,6 +2134,62 @@ function buildWidgetExtendedProperties(prop) {
1539
2134
  }
1540
2135
  return extended;
1541
2136
  }
2137
+ function isRelatedEntityIncludedOnMasterForm(related, kind, placementKind) {
2138
+ if (kind !== 'create' && kind !== 'update') {
2139
+ return true;
2140
+ }
2141
+ const defaultInclusive = placementKind === 'merge-detail';
2142
+ if (kind === 'create') {
2143
+ const flag = related.appearOn?.create;
2144
+ return defaultInclusive ? flag !== false : flag === true;
2145
+ }
2146
+ const flag = related.appearOn?.update;
2147
+ return defaultInclusive ? flag !== false : flag === true;
2148
+ }
2149
+ function collectListRelatedEntitiesForFormWizard(entity, kind) {
2150
+ return (entity.relatedEntities ?? [])
2151
+ .filter((re) => !re.hidden &&
2152
+ (re.layout?.type === 'tab-list' || re.layout?.type === 'page-list') &&
2153
+ isRelatedEntityIncludedOnMasterForm(re, kind, 'list'))
2154
+ .sort((a, b) => (a.layout?.order ?? 0) - (b.layout?.order ?? 0));
2155
+ }
2156
+ function buildEntityFormRelatedStepId(re) {
2157
+ const safe = re.entity.replace(/[^a-zA-Z0-9._-]/g, '_');
2158
+ return `related-${safe}-${re.layout?.order ?? 0}`;
2159
+ }
2160
+ function configureEntityFormWizardFooterActions(ab, wizardName, firstStepContinueTitleKey) {
2161
+ ab.custom({
2162
+ title: '@general:actions.previous.title',
2163
+ command: `widget:${wizardName}.previous`,
2164
+ icon: 'fa-regular fa-arrow-left',
2165
+ color: 'primary',
2166
+ position: 'suffix',
2167
+ disabled: '{{api.getStatus().isFirst}}',
2168
+ });
2169
+ ab.custom({
2170
+ title: firstStepContinueTitleKey,
2171
+ command: { name: ENTITY_FORM_ACTION_FIRST_STEP_CONTINUE },
2172
+ color: 'primary',
2173
+ position: 'suffix',
2174
+ hidden: '{{!api.getStatus().isFirst}}',
2175
+ predicateApiWidgetName: wizardName,
2176
+ });
2177
+ ab.custom({
2178
+ title: '@general:actions.next.title',
2179
+ command: `widget:${wizardName}.next`,
2180
+ color: 'primary',
2181
+ position: 'suffix',
2182
+ hidden: '{{api.getStatus().isFirst || api.getStatus().isLast}}',
2183
+ });
2184
+ ab.custom({
2185
+ title: '@general:entity-form.done.title',
2186
+ command: { name: ENTITY_FORM_ACTION_DONE },
2187
+ color: 'primary',
2188
+ position: 'suffix',
2189
+ hidden: '{{!api.getStatus().isLast}}',
2190
+ predicateApiWidgetName: wizardName,
2191
+ });
2192
+ }
1542
2193
  function getGroupTitle(entity, groupId) {
1543
2194
  const g = (entity.groups || []).find((x) => x.id === groupId);
1544
2195
  return g?.title;
@@ -1727,6 +2378,11 @@ var openEntityDetails_command = /*#__PURE__*/Object.freeze({
1727
2378
  AXPOpenEntityDetailsCommand: AXPOpenEntityDetailsCommand
1728
2379
  });
1729
2380
 
2381
+ /** Matches persisted PK checks used for choose update vs create (strict types only). */
2382
+ function hasPersistedRootId(context) {
2383
+ const raw = context?.id ?? context?._id;
2384
+ return (typeof raw === 'string' && raw.length > 0) || (typeof raw === 'number' && !Number.isNaN(raw));
2385
+ }
1730
2386
  class AXPCreateEntityCommand {
1731
2387
  constructor() {
1732
2388
  this.entityForm = inject(AXPEntityFormBuilderService);
@@ -1760,7 +2416,6 @@ class AXPCreateEntityCommand {
1760
2416
  const entityRef = await this.entityService.resolve(moduleName, entityName);
1761
2417
  let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(data);
1762
2418
  chain.actions((actions) => {
1763
- actions.cancel('@general:actions.cancel.title');
1764
2419
  actions.submit('@general:actions.create.title');
1765
2420
  });
1766
2421
  if (excludeProperties && excludeProperties.length > 0) {
@@ -1785,25 +2440,31 @@ class AXPCreateEntityCommand {
1785
2440
  }
1786
2441
  const result = await chain
1787
2442
  .onAction(async (dialogRef) => {
1788
- if (dialogRef.action() === 'cancel') {
1789
- return { success: false };
1790
- }
1791
2443
  const createFn = entityRef.commands?.create?.execute;
1792
- if (!createFn) {
1793
- const msg = await this.translationService.translateAsync('@general:messages.entity.create-command-unavailable');
2444
+ const updateFn = entityRef.commands?.update?.execute;
2445
+ const context = dialogRef.context();
2446
+ console.log('context', context);
2447
+ const hasPersistedId = hasPersistedRootId(context);
2448
+ const persistFn = hasPersistedId ? updateFn : createFn;
2449
+ const missingPersistHandlerMsg = hasPersistedId
2450
+ ? await this.translationService.translateAsync('@general:messages.entity.update-command-unavailable')
2451
+ : await this.translationService.translateAsync('@general:messages.entity.create-command-unavailable');
2452
+ const failedMsgKey = hasPersistedId
2453
+ ? '@general:messages.entity.update-failed'
2454
+ : '@general:messages.entity.create-failed';
2455
+ if (!persistFn) {
1794
2456
  if (enableOperationToasts) {
1795
2457
  this.toastService.show({
1796
2458
  color: 'danger',
1797
2459
  title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
1798
- content: msg,
2460
+ content: missingPersistHandlerMsg,
1799
2461
  });
1800
2462
  }
1801
- throw new Error(msg);
2463
+ throw new Error(missingPersistHandlerMsg);
1802
2464
  }
1803
2465
  dialogRef.setLoading(true);
1804
2466
  try {
1805
- const context = dialogRef.context();
1806
- const result = await createFn(context);
2467
+ const result = await persistFn(context);
1807
2468
  if (result) {
1808
2469
  return {
1809
2470
  success: true,
@@ -1817,13 +2478,13 @@ class AXPCreateEntityCommand {
1817
2478
  return {
1818
2479
  success: false,
1819
2480
  message: {
1820
- text: await this.translationService.translateAsync('@general:messages.entity.create-failed'),
2481
+ text: await this.translationService.translateAsync(failedMsgKey),
1821
2482
  },
1822
2483
  };
1823
2484
  }
1824
2485
  }
1825
2486
  catch (e) {
1826
- const errorMsg = e.message ?? (await this.translationService.translateAsync('@general:messages.entity.create-failed'));
2487
+ const errorMsg = e.message ?? (await this.translationService.translateAsync(failedMsgKey));
1827
2488
  if (enableOperationToasts) {
1828
2489
  this.toastService.show({
1829
2490
  color: 'danger',
@@ -1911,6 +2572,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
1911
2572
  summary: 'Dialog submit persists entity; navigation/toast behavior per entity form builder and settings.',
1912
2573
  derivesFrom: 'AXPExecuteCommandResult<any> from entity form chain and AXPOpenEntityDetailsCommand',
1913
2574
  },
2575
+ capabilities: ['ai'],
1914
2576
  ai: {
1915
2577
  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
2578
  usage: {
@@ -1925,6 +2587,7 @@ const axpCreateEntityCommandDefinition = defineCommand({
1925
2587
  tags: ['entity', 'create', 'form', 'dialog', 'human-in-the-loop', 'mock', 'entity-storage', 'wait-user'],
1926
2588
  toolInputDefaults: { ...axpCreateEntityAiToolInputDefaults },
1927
2589
  },
2590
+ categories: [AXP_COMMAND_DEFINITION_CATEGORY_ENTITY],
1928
2591
  });
1929
2592
 
1930
2593
  class AXPUpdateEntityCommand {
@@ -1959,9 +2622,7 @@ class AXPUpdateEntityCommand {
1959
2622
  };
1960
2623
  }
1961
2624
  const entityRef = await this.entityService.resolve(moduleName, entityName);
1962
- let dialogRef;
1963
2625
  try {
1964
- // Validate data type
1965
2626
  if (!data || (typeof data !== 'string' && typeof data !== 'object')) {
1966
2627
  return {
1967
2628
  success: false,
@@ -1970,10 +2631,8 @@ class AXPUpdateEntityCommand {
1970
2631
  },
1971
2632
  };
1972
2633
  }
1973
- // Pass data directly to update() - it handles string | object internally
1974
2634
  let chain = this.entityForm.entity(`${moduleName}.${entityName}`).update(data);
1975
2635
  chain.actions((actions) => {
1976
- actions.cancel('@general:actions.cancel.title');
1977
2636
  actions.submit('@general:actions.apply.title');
1978
2637
  });
1979
2638
  if (excludeProperties && excludeProperties.length > 0) {
@@ -1994,19 +2653,8 @@ class AXPUpdateEntityCommand {
1994
2653
  if (finalSize) {
1995
2654
  chain.size(finalSize);
1996
2655
  }
1997
- dialogRef = await chain.show();
1998
- if (dialogRef.action() === 'cancel') {
1999
- dialogRef.close();
2000
- return {
2001
- success: false,
2002
- // message: {
2003
- // text: await this.translationService.translateAsync('@general:messages.generic.cancel.description'),
2004
- // },
2005
- };
2006
- }
2007
- else if (dialogRef.action() === 'submit') {
2008
- dialogRef.setLoading(true);
2009
- const context = dialogRef.context();
2656
+ return (await chain
2657
+ .onAction(async (dialogRef) => {
2010
2658
  const updateFn = entityRef.commands?.update?.execute;
2011
2659
  if (!updateFn) {
2012
2660
  const msg = await this.translationService.translateAsync('@general:messages.entity.update-command-unavailable');
@@ -2017,35 +2665,44 @@ class AXPUpdateEntityCommand {
2017
2665
  content: msg,
2018
2666
  });
2019
2667
  }
2020
- return {
2021
- success: false,
2022
- message: { text: msg },
2023
- };
2668
+ throw new Error(msg);
2024
2669
  }
2025
- const result = await updateFn(context);
2026
- if (result) {
2027
- dialogRef.close();
2670
+ dialogRef.setLoading(true);
2671
+ try {
2672
+ const context = dialogRef.context();
2673
+ const result = await updateFn(context);
2674
+ if (result) {
2675
+ return {
2676
+ success: true,
2677
+ data: result.data ?? result,
2678
+ message: {
2679
+ text: await this.translationService.translateAsync('@general:messages.generic.success.description'),
2680
+ },
2681
+ };
2682
+ }
2028
2683
  return {
2029
- success: true,
2030
- data: result,
2684
+ success: false,
2031
2685
  message: {
2032
- text: await this.translationService.translateAsync('@general:messages.generic.success.description'),
2686
+ text: await this.translationService.translateAsync('@general:messages.entity.command-no-result'),
2033
2687
  },
2034
2688
  };
2035
2689
  }
2036
- return (result ?? {
2037
- success: false,
2038
- message: {
2039
- text: await this.translationService.translateAsync('@general:messages.entity.command-no-result'),
2040
- },
2041
- });
2042
- }
2043
- return {
2044
- success: false,
2045
- message: {
2046
- text: await this.translationService.translateAsync('@general:messages.entity.invalid-action'),
2047
- },
2048
- };
2690
+ catch (e) {
2691
+ const errorMsg = e.message ?? (await this.translationService.translateAsync('@general:messages.entity.update-failed'));
2692
+ if (enableOperationToasts) {
2693
+ this.toastService.show({
2694
+ color: 'danger',
2695
+ title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
2696
+ content: errorMsg,
2697
+ });
2698
+ }
2699
+ throw e;
2700
+ }
2701
+ finally {
2702
+ dialogRef.setLoading(false);
2703
+ }
2704
+ })
2705
+ .show());
2049
2706
  }
2050
2707
  catch (error) {
2051
2708
  const text = error instanceof Error
@@ -2063,11 +2720,6 @@ class AXPUpdateEntityCommand {
2063
2720
  message: { text },
2064
2721
  };
2065
2722
  }
2066
- finally {
2067
- if (dialogRef) {
2068
- dialogRef.setLoading(false);
2069
- }
2070
- }
2071
2723
  }
2072
2724
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2073
2725
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityCommand, providedIn: 'root' }); }
@@ -2193,6 +2845,18 @@ class AXPEntityDetailPopoverComponent {
2193
2845
  this.entityDetails = signal(null, ...(ngDevMode ? [{ debugName: "entityDetails" }] : /* istanbul ignore next */ []));
2194
2846
  this.isLoadingDetails = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingDetails" }] : /* istanbul ignore next */ []));
2195
2847
  this.isDetailPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDetailPopoverOpen" }] : /* istanbul ignore next */ []));
2848
+ /**
2849
+ * Stable list of property widgets for the template. Must be a signal (computed), not a method:
2850
+ * calling a method from the template rebuilds nodes every CD cycle and can make the widget renderer loop.
2851
+ */
2852
+ this.entityPropertiesWithWidgets = computed(() => {
2853
+ const details = this.entityDetails();
2854
+ const data = details?.entityData;
2855
+ const entityDefinition = details?.entityDefinition;
2856
+ if (!data || !entityDefinition?.properties)
2857
+ return [];
2858
+ return this.buildEntityPropertiesWithWidgets(data, entityDefinition, this.textField(), this.valueField());
2859
+ }, ...(ngDevMode ? [{ debugName: "entityPropertiesWithWidgets" }] : /* istanbul ignore next */ []));
2196
2860
  }
2197
2861
  //#endregion
2198
2862
  //#region ---- Public Methods ----
@@ -2347,16 +3011,12 @@ class AXPEntityDetailPopoverComponent {
2347
3011
  }
2348
3012
  return 0;
2349
3013
  }
2350
- getEntityPropertiesWithWidgets() {
2351
- const data = this.entityDetails()?.entityData;
2352
- const entityDefinition = this.entityDetails()?.entityDefinition;
2353
- if (!data || !entityDefinition?.properties)
2354
- return [];
3014
+ buildEntityPropertiesWithWidgets(data, entityDefinition, textFieldValue, valueFieldValue) {
2355
3015
  // Use properties (not columns) for correct titles and schema; columns may have dataPath that doesn't match
2356
- const importantProperties = entityDefinition.properties
3016
+ return entityDefinition.properties
2357
3017
  .filter((prop) => {
2358
3018
  // Exclude technical fields
2359
- if (prop.name === 'id' || prop.name === this.textField() || prop.name === this.valueField()) {
3019
+ if (prop.name === 'id' || prop.name === textFieldValue || prop.name === valueFieldValue) {
2360
3020
  return false;
2361
3021
  }
2362
3022
  // Only include properties that have a meaningful value (using get for dotted paths)
@@ -2424,7 +3084,6 @@ class AXPEntityDetailPopoverComponent {
2424
3084
  node: widgetNode,
2425
3085
  };
2426
3086
  });
2427
- return importantProperties;
2428
3087
  }
2429
3088
  /**
2430
3089
  * Resolves the data path for a property. For lookups with expose, returns the expanded path
@@ -2447,11 +3106,11 @@ class AXPEntityDetailPopoverComponent {
2447
3106
  return prop.name;
2448
3107
  }
2449
3108
  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 }); }
3109
+ 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
3110
  }
2452
3111
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
2453
3112
  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" }]
3113
+ 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
3114
  }], 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
3115
 
2457
3116
  class AXPEntityDetailPopoverService {
@@ -3265,82 +3924,6 @@ class AXPEntityListViewColumnViewModel {
3265
3924
  }
3266
3925
  }
3267
3926
 
3268
- //#endregion
3269
- //#region ---- Helpers ----
3270
- function isRelatedEntityStringColumns(columns) {
3271
- return columns.length === 0 || typeof columns[0] === 'string';
3272
- }
3273
- /**
3274
- * Property names from `AXPRelatedEntity.columns` (no expression evaluation).
3275
- * Empty array means caller can treat as “no name filter” (show all allowed by entity).
3276
- */
3277
- function getRelatedEntityColumnNames(columns) {
3278
- if (!columns?.length) {
3279
- return [];
3280
- }
3281
- if (isRelatedEntityStringColumns(columns)) {
3282
- return [...columns];
3283
- }
3284
- return columns.map((c) => c.name);
3285
- }
3286
- function isTruthyVisible(value) {
3287
- if (value === true) {
3288
- return true;
3289
- }
3290
- if (value === false || value === null || value === undefined) {
3291
- return false;
3292
- }
3293
- if (typeof value === 'string') {
3294
- const t = value.trim().toLowerCase();
3295
- return t !== '' && t !== 'false' && t !== '0';
3296
- }
3297
- if (typeof value === 'number') {
3298
- return value !== 0 && !Number.isNaN(value);
3299
- }
3300
- return Boolean(value);
3301
- }
3302
- //#endregion
3303
- //#region ---- Public API ----
3304
- /**
3305
- * Resolves `AXPRelatedEntity.columns` for `entity-list`:
3306
- * - `string[]` → `includeColumns` only (legacy).
3307
- * - `AXPEntityTableColumn[]` → evaluates `options.visible` when it is a string (parent detail context),
3308
- * drops hidden / invisible columns, and returns `relatedTableColumns` + matching `includeColumns`.
3309
- */
3310
- async function resolveRelatedEntityColumns(columns, expressionEvaluator, scope) {
3311
- if (!columns?.length) {
3312
- return {};
3313
- }
3314
- if (isRelatedEntityStringColumns(columns)) {
3315
- return { includeColumns: getRelatedEntityColumnNames(columns) };
3316
- }
3317
- const relatedTableColumns = [];
3318
- for (const raw of columns) {
3319
- const col = cloneDeep(raw);
3320
- if (col.hidden === true) {
3321
- continue;
3322
- }
3323
- let visible = col.options?.visible;
3324
- if (visible === undefined) {
3325
- visible = true;
3326
- }
3327
- else if (typeof visible === 'string') {
3328
- const evaluated = await expressionEvaluator.evaluate(visible, scope);
3329
- visible = isTruthyVisible(evaluated);
3330
- }
3331
- if (visible === false) {
3332
- continue;
3333
- }
3334
- col.options = { ...col.options, visible: true };
3335
- relatedTableColumns.push(col);
3336
- }
3337
- return {
3338
- relatedTableColumns,
3339
- includeColumns: relatedTableColumns.map((c) => c.name),
3340
- };
3341
- }
3342
- //#endregion
3343
-
3344
3927
  class AXPEntityDetailListViewModel {
3345
3928
  constructor(injector, detailEntityConfig, parent) {
3346
3929
  this.injector = injector;
@@ -4151,6 +4734,7 @@ class AXPEntityMasterListViewModel {
4151
4734
  return this.sortedFields().filter((i) => i.dir).length;
4152
4735
  };
4153
4736
  this.sortedFields = signal([], ...(ngDevMode ? [{ debugName: "sortedFields" }] : /* istanbul ignore next */ []));
4737
+ //#endregion
4154
4738
  this.evaluateExpressions = async (options, data) => {
4155
4739
  const scope = this.createExpressionScope(data);
4156
4740
  return await this.expressionEvaluator.evaluate(options, scope);
@@ -4240,7 +4824,6 @@ class AXPEntityMasterListViewModel {
4240
4824
  width: c.width,
4241
4825
  }));
4242
4826
  set(newSettings, `list.views.${this.view().name}.columns`, updatedColumns);
4243
- console.log(newSettings, `list.views.${this.view().name}.columns`);
4244
4827
  return newSettings;
4245
4828
  });
4246
4829
  break;
@@ -4623,7 +5206,8 @@ class AXPEntityMasterListViewModel {
4623
5206
  const command = resolvedTriggerName.split('&')[0];
4624
5207
  const options = await this.evaluateExpressions(action?.options, data);
4625
5208
  const baseData = action?.scope == AXPEntityCommandScope.Selected ? this.selectedItems() : data;
4626
- const commandData = this.mergeDefaultCategoryForCreate(command, baseData);
5209
+ let commandData = this.mergeViewConditionsForCreate(command, baseData);
5210
+ commandData = this.mergeDefaultCategoryForCreate(command, commandData);
4627
5211
  if (this.workflow.exists(command)) {
4628
5212
  await this.workflow.execute(command, {
4629
5213
  entity: getEntityInfo(this.entityDef).source,
@@ -4705,6 +5289,38 @@ class AXPEntityMasterListViewModel {
4705
5289
  }
4706
5290
  return baseData;
4707
5291
  }
5292
+ //#region ---- Create command defaults from list view ----
5293
+ /**
5294
+ * Seeds create payloads with fixed list-view conditions (e.g. scope=T on the tenant dashboards view)
5295
+ * so new rows match the view context and downstream queries (such as home dashboard) can find them.
5296
+ */
5297
+ mergeViewConditionsForCreate(command, baseData) {
5298
+ const isCreateCommand = command === 'Entity:Create' || command === 'create-entity';
5299
+ if (!isCreateCommand) {
5300
+ return baseData;
5301
+ }
5302
+ const conditions = this.view()?.conditions ?? [];
5303
+ if (conditions.length === 0) {
5304
+ return baseData;
5305
+ }
5306
+ const seed = {};
5307
+ for (const c of conditions) {
5308
+ if (!c?.name || c.operator?.type !== 'equal' || c.value === undefined) {
5309
+ continue;
5310
+ }
5311
+ seed[c.name] = c.value;
5312
+ }
5313
+ if (Object.keys(seed).length === 0) {
5314
+ return baseData;
5315
+ }
5316
+ if (baseData == null) {
5317
+ return seed;
5318
+ }
5319
+ if (typeof baseData === 'object' && !Array.isArray(baseData)) {
5320
+ return { ...seed, ...baseData };
5321
+ }
5322
+ return baseData;
5323
+ }
4708
5324
  async execute(command) {
4709
5325
  switch (command?.name) {
4710
5326
  case 'navigate':
@@ -9858,7 +10474,7 @@ class AXPEntityListTableService {
9858
10474
  animation: true,
9859
10475
  },
9860
10476
  // 🎪 Events
9861
- ...this.createDefaultEvents(entity, allActions),
10477
+ ...this.createDefaultEvents(entity, allActions, options?.excludeProperties),
9862
10478
  };
9863
10479
  console.log('listOptions', listOptions);
9864
10480
  return listOptions;
@@ -9948,7 +10564,7 @@ class AXPEntityListTableService {
9948
10564
  /**
9949
10565
  * Handle execution of a row command (shared by double-click and command handlers)
9950
10566
  */
9951
- async handleRowCommand(e, selectedRows, entity, allActions) {
10567
+ async handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties) {
9952
10568
  const data = e.data;
9953
10569
  const commandName = e.name;
9954
10570
  const action = allActions.find((c) => {
@@ -9959,7 +10575,8 @@ class AXPEntityListTableService {
9959
10575
  c.scope == AXPEntityCommandScope.TypeLevel));
9960
10576
  });
9961
10577
  const command = commandName.split('&')[0];
9962
- const options = await this.evaluateExpressions(action?.options, data);
10578
+ const evaluatedOptions = await this.evaluateExpressions(action?.options, data);
10579
+ const options = this.mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties);
9963
10580
  if (this.commandService.exists(command)) {
9964
10581
  await this.commandService.execute(command, {
9965
10582
  __context__: {
@@ -9995,10 +10612,25 @@ class AXPEntityListTableService {
9995
10612
  });
9996
10613
  }
9997
10614
  }
10615
+ /**
10616
+ * When a related entity list declares `excludeProperties`, row commands bypass the details page
10617
+ * `execute()` merge — apply the same exclusions for embedded create/update commands.
10618
+ */
10619
+ mergeRelatedListFormOptions(command, evaluatedOptions, relatedListExcludeProperties) {
10620
+ const exclusions = relatedListExcludeProperties?.filter(Boolean);
10621
+ if (!exclusions?.length ||
10622
+ (command !== 'Entity:Create' && command !== 'Entity:Update')) {
10623
+ return evaluatedOptions;
10624
+ }
10625
+ return {
10626
+ ...evaluatedOptions,
10627
+ excludeProperties: exclusions,
10628
+ };
10629
+ }
9998
10630
  /**
9999
10631
  * Create default events
10000
10632
  */
10001
- createDefaultEvents(entity, allActions) {
10633
+ createDefaultEvents(entity, allActions, relatedListExcludeProperties) {
10002
10634
  return {
10003
10635
  onRowClick: (row) => {
10004
10636
  console.log('Entity List - Row clicked:', row);
@@ -10017,13 +10649,13 @@ class AXPEntityListTableService {
10017
10649
  name: defaultAction.name,
10018
10650
  data: e.data,
10019
10651
  };
10020
- this.handleRowCommand(d, undefined, entity, allActions);
10652
+ this.handleRowCommand(d, undefined, entity, allActions, relatedListExcludeProperties);
10021
10653
  },
10022
10654
  onSelectionChange: (selectedRows) => {
10023
10655
  console.log('Entity List - Selection changed:', selectedRows);
10024
10656
  },
10025
10657
  onRowCommand: async (e, selectedRows) => {
10026
- await this.handleRowCommand(e, selectedRows, entity, allActions);
10658
+ await this.handleRowCommand(e, selectedRows, entity, allActions, relatedListExcludeProperties);
10027
10659
  },
10028
10660
  };
10029
10661
  }
@@ -10330,6 +10962,42 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10330
10962
  }));
10331
10963
  return actions;
10332
10964
  }, ...(ngDevMode ? [{ debugName: "secondaryActions" }] : /* istanbul ignore next */ []));
10965
+ /** Keeps related-list filters in sync with dialog context for create wizards (main id appears after first-step save). */
10966
+ this.#relatedFilterSyncEffect = effect(() => {
10967
+ const opts = this.options();
10968
+ if (!opts['syncRelatedListFiltersFromDialogContext']) {
10969
+ return;
10970
+ }
10971
+ const specs = opts['relatedFilterConditionSpecs'];
10972
+ if (!specs?.length) {
10973
+ return;
10974
+ }
10975
+ this.contextService.data();
10976
+ if (!this.fullPath()) {
10977
+ return;
10978
+ }
10979
+ untracked(() => void this.applyRelatedFiltersFromContextAndDatasource(specs));
10980
+ }, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
10981
+ /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
10982
+ this.#patchDataListRefreshEffect = effect(() => {
10983
+ const inst = this.listWidget()?.instance;
10984
+ const opts = this.options();
10985
+ if (!inst?.refresh || inst.__axpEntityListRefreshPatched || !opts['syncRelatedListFiltersFromDialogContext']) {
10986
+ return;
10987
+ }
10988
+ inst.__axpEntityListRefreshPatched = true;
10989
+ const originalRefresh = inst.refresh.bind(inst);
10990
+ inst.refresh = () => {
10991
+ void (async () => {
10992
+ const o = this.options();
10993
+ const specs = o['relatedFilterConditionSpecs'];
10994
+ if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
10995
+ await this.applyRelatedFiltersFromContextAndDatasource(specs);
10996
+ }
10997
+ originalRefresh();
10998
+ })();
10999
+ };
11000
+ }, ...(ngDevMode ? [{ debugName: "#patchDataListRefreshEffect" }] : /* istanbul ignore next */ []));
10333
11001
  //#region ---- Query Change Handler ----
10334
11002
  this.queries = undefined;
10335
11003
  this.#effect = effect(() => {
@@ -10380,7 +11048,32 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10380
11048
  const commandData = action?.scope == AXPEntityCommandScope.Selected
10381
11049
  ? this.selectedItems()
10382
11050
  : action?.options?.['process']?.data || null;
10383
- const options = await this.evaluateToolbarExpressions(action?.options, commandData);
11051
+ let options = await this.evaluateToolbarExpressions(action?.options, commandData);
11052
+ const listOpts = this.options();
11053
+ const foreignKeyFieldName = listOpts['foreignKeyField'];
11054
+ const createExcludes = listOpts['nestedCreateHiddenProperties'] ?? [];
11055
+ if (command === 'Entity:Create' && foreignKeyFieldName) {
11056
+ const root = this.contextService.snapshot();
11057
+ const idVal = get(root, 'id');
11058
+ options = merge({}, options);
11059
+ options['process'] = merge({}, options['process'], {
11060
+ data: merge({}, options['process']?.data ?? {}, {
11061
+ ...(idVal !== undefined && idVal !== null && idVal !== '' ? { [foreignKeyFieldName]: idVal } : {}),
11062
+ }),
11063
+ });
11064
+ }
11065
+ if (command === 'Entity:Create' && createExcludes.length) {
11066
+ const existing = (options['excludeProperties'] ?? []).slice();
11067
+ options = merge({}, options);
11068
+ options['excludeProperties'] = [...new Set([...createExcludes, ...existing])];
11069
+ }
11070
+ const relatedExcludes = listOpts['excludeProperties'];
11071
+ const exclusionsFromList = relatedExcludes?.filter(Boolean) ?? [];
11072
+ if (exclusionsFromList.length && (command === 'Entity:Create' || command === 'Entity:Update')) {
11073
+ const existing = (options['excludeProperties'] ?? []).slice();
11074
+ options = merge({}, options);
11075
+ options['excludeProperties'] = [...new Set([...existing, ...exclusionsFromList])];
11076
+ }
10384
11077
  if (this.commandService.exists(command)) {
10385
11078
  await this.commandService.execute(command, {
10386
11079
  __context__: {
@@ -10420,13 +11113,88 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10420
11113
  if (!opts) {
10421
11114
  return {};
10422
11115
  }
11116
+ const root = this.contextService.snapshot();
10423
11117
  const scope = {
10424
11118
  context: {
10425
- eval: (path) => get(expressionData, path),
11119
+ eval: (path) => get(root, path) ?? get(expressionData, path),
10426
11120
  },
10427
11121
  };
10428
11122
  return (await this.expressionEvaluator.evaluate(opts, scope));
10429
11123
  }
11124
+ /**
11125
+ * Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
11126
+ */
11127
+ async applyRelatedFiltersFromContext(specs) {
11128
+ const root = this.contextService.snapshot();
11129
+ const scope = {
11130
+ context: {
11131
+ eval: (path) => get(root, path),
11132
+ },
11133
+ };
11134
+ const filters = await Promise.all(specs.map(async (c) => ({
11135
+ field: c.name,
11136
+ operator: c.operator,
11137
+ value: await this.expressionEvaluator.evaluate(c.value, scope),
11138
+ hidden: true,
11139
+ })));
11140
+ if (!this.fullPath()) {
11141
+ return;
11142
+ }
11143
+ const current = this.getValue();
11144
+ if (isEqual$1(current?.toolbar?.filters, filters)) {
11145
+ return;
11146
+ }
11147
+ this.setValue({
11148
+ ...current,
11149
+ toolbar: { ...(current?.toolbar ?? {}), filters },
11150
+ });
11151
+ }
11152
+ /** Keeps related-list filters in sync with dialog context for create wizards (main id appears after first-step save). */
11153
+ #relatedFilterSyncEffect;
11154
+ /**
11155
+ * Pushes current toolbar filters to the embedded list data source when both exist.
11156
+ * Returns false while data-list is still mounting (see deferred listNode.set in ngOnInit).
11157
+ */
11158
+ pushToolbarFiltersToDataSource() {
11159
+ const listInstance = this.listWidget()?.instance;
11160
+ const dataSource = listInstance?.options?.()?.['dataSource'];
11161
+ const toolbar = this.getValue()?.toolbar;
11162
+ if (!dataSource?.filter || !toolbar?.filters?.length) {
11163
+ return false;
11164
+ }
11165
+ dataSource.filter({ filters: toolbar.filters });
11166
+ return true;
11167
+ }
11168
+ /**
11169
+ * Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
11170
+ */
11171
+ async applyRelatedFiltersFromContextAndDatasource(specs) {
11172
+ await this.applyRelatedFiltersFromContext(specs);
11173
+ if (this.pushToolbarFiltersToDataSource()) {
11174
+ return;
11175
+ }
11176
+ const opts = this.options();
11177
+ if (!opts['syncRelatedListFiltersFromDialogContext']) {
11178
+ return;
11179
+ }
11180
+ /** Data-list is created in ngOnInit via deferred listNode.set; retry briefly until instance exposes dataSource. */
11181
+ const maxAttempts = 40;
11182
+ const delayMs = 50;
11183
+ for (let i = 0; i < maxAttempts; i++) {
11184
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
11185
+ if (this.pushToolbarFiltersToDataSource()) {
11186
+ return;
11187
+ }
11188
+ }
11189
+ }
11190
+ /**
11191
+ * Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
11192
+ */
11193
+ refreshGridWithParentScopedFilters() {
11194
+ this.listWidget()?.instance?.call('refresh');
11195
+ }
11196
+ /** Patches data-list `refresh` so the grid footer / toolbar refresh keeps parent-scoped filters on the data source. */
11197
+ #patchDataListRefreshEffect;
10430
11198
  #effect;
10431
11199
  /**
10432
11200
  * Validates that all required dependencies are available
@@ -10529,6 +11297,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10529
11297
  includeColumns: this.includeColumns(),
10530
11298
  relatedTableColumns: this.relatedTableColumns(),
10531
11299
  customFilterDefinitions: this.customFilterDefinitions(),
11300
+ excludeProperties: this.options()['excludeProperties'],
10532
11301
  };
10533
11302
  const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
10534
11303
  const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
@@ -10552,6 +11321,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10552
11321
  columns: this.getValue()?.toolbar?.columns,
10553
11322
  },
10554
11323
  });
11324
+ queueMicrotask(() => {
11325
+ void this.pushToolbarFiltersToDataSource();
11326
+ });
10555
11327
  }, 100);
10556
11328
  }
10557
11329
  async ngAfterViewInit() {
@@ -10560,7 +11332,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10560
11332
  .pipe(takeUntil(this.destroyed))
10561
11333
  .subscribe((event) => {
10562
11334
  if (event.payload.entity == this.entitySource()) {
10563
- this.listWidget()?.instance.call('refresh');
11335
+ this.refreshGridWithParentScopedFilters();
10564
11336
  }
10565
11337
  });
10566
11338
  this.eventService
@@ -10568,7 +11340,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
10568
11340
  .pipe(takeUntil(this.destroyed))
10569
11341
  .subscribe((e) => {
10570
11342
  if (e.data.name == `${this.entity()?.module}.${this.entity()?.name}`) {
10571
- this.listWidget()?.instance.call('refresh');
11343
+ this.refreshGridWithParentScopedFilters();
10572
11344
  }
10573
11345
  });
10574
11346
  const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
@@ -10846,7 +11618,7 @@ const AXPEntityListWidget = {
10846
11618
  type: 'view',
10847
11619
  categories: [],
10848
11620
  groups: [AXPWidgetGroupEnum.EntityWidget],
10849
- icon: 'fa-solid fa-square',
11621
+ icon: 'fa-light fa-square',
10850
11622
  properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY],
10851
11623
  components: {
10852
11624
  view: {
@@ -12145,7 +12917,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
12145
12917
  const prevValue = this.previousValue();
12146
12918
  const isInitialized = this.initialized();
12147
12919
  // Check if value has actually changed
12148
- const valueChanged = !isEqual(currentValue, prevValue);
12920
+ const valueChanged = !isEqual$1(currentValue, prevValue);
12149
12921
  // Determine if we should update:
12150
12922
  // 1. First initialization with a value
12151
12923
  // 2. Value becomes empty after initialization
@@ -12296,67 +13068,53 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
12296
13068
  }
12297
13069
  }
12298
13070
  /**
12299
- * Update expose data for empty state
13071
+ * Writes expose targets into context using per-path updates.
13072
+ * Avoids `contextService.patch()` with nested objects: patch shallow-merges top-level keys only,
13073
+ * so e.g. `{ person: { educationLevel: { id, title } } }` would replace the entire `person`
13074
+ * object and drop sibling fields like `person.educationLevelId`, causing a value/effect loop.
12300
13075
  */
12301
13076
  expoesItems() {
12302
13077
  const exposeValue = castArray(this.expose());
12303
- const itemToExpose = {};
12304
13078
  const items = this.selectedItems();
12305
13079
  const isEmpty = !items || items.length === 0;
12306
- // If items are empty, group expose configs by parent path and set parent to null
12307
13080
  if (isEmpty) {
12308
13081
  const parentPaths = new Set();
12309
13082
  exposeValue.forEach((i) => {
12310
13083
  if (typeof i === 'string') {
12311
- // For string expose, the path itself is the target
12312
13084
  const pathParts = i.split('.');
12313
13085
  if (pathParts.length > 1) {
12314
- // Extract parent path (everything except the last part)
12315
- const parentPath = pathParts.slice(0, -1).join('.');
12316
- parentPaths.add(parentPath);
13086
+ parentPaths.add(pathParts.slice(0, -1).join('.'));
12317
13087
  }
12318
13088
  else {
12319
- // Single level path, set directly to null
12320
- setSmart(itemToExpose, i, null);
13089
+ this.contextService.update(i, null);
12321
13090
  }
12322
13091
  }
12323
13092
  else {
12324
- // For object expose, extract parent path from target
12325
13093
  const pathParts = i.target.split('.');
12326
13094
  if (pathParts.length > 1) {
12327
- // Extract parent path (everything except the last part)
12328
- const parentPath = pathParts.slice(0, -1).join('.');
12329
- parentPaths.add(parentPath);
13095
+ parentPaths.add(pathParts.slice(0, -1).join('.'));
12330
13096
  }
12331
13097
  else {
12332
- // Single level path, set directly to null
12333
- setSmart(itemToExpose, i.target, null);
13098
+ this.contextService.update(i.target, null);
12334
13099
  }
12335
13100
  }
12336
13101
  });
12337
- // Set all parent paths to null
12338
13102
  parentPaths.forEach((parentPath) => {
12339
- setSmart(itemToExpose, parentPath, null);
12340
- });
12341
- }
12342
- else {
12343
- // Normal processing when items exist
12344
- exposeValue.forEach((i) => {
12345
- if (typeof i == 'string') {
12346
- const values = items.map((item) => set({}, i, get(item, i)));
12347
- setSmart(itemToExpose, i, this.singleOrMultiple(values));
12348
- }
12349
- else {
12350
- // extract data from item by source path and set context by target path
12351
- const values = this.multiple()
12352
- ? items.map((item) => set({}, i.source, get(item, i.source)))
12353
- : items.map((item) => get(item, i.source));
12354
- setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
12355
- }
13103
+ this.contextService.update(parentPath, null);
12356
13104
  });
13105
+ return;
12357
13106
  }
12358
- setTimeout(() => {
12359
- this.contextService.patch(itemToExpose, true);
13107
+ exposeValue.forEach((i) => {
13108
+ if (typeof i === 'string') {
13109
+ const values = items.map((item) => set({}, i, get(item, i)));
13110
+ this.contextService.update(i, this.singleOrMultiple(values));
13111
+ }
13112
+ else {
13113
+ const values = this.multiple()
13114
+ ? items.map((item) => set({}, i.source, get(item, i.source)))
13115
+ : items.map((item) => get(item, i.source));
13116
+ this.contextService.update(i.target, this.singleOrMultiple(values));
13117
+ }
12360
13118
  });
12361
13119
  }
12362
13120
  singleOrMultiple(values) {
@@ -12517,36 +13275,47 @@ var lookupWidgetEdit_component = /*#__PURE__*/Object.freeze({
12517
13275
 
12518
13276
  class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12519
13277
  constructor() {
12520
- super(...arguments);
12521
- //#region ---- Dependencies ----
13278
+ super();
13279
+ //#region ---- Dependencies ----
12522
13280
  this.entityDetailPopoverService = inject(AXPEntityDetailPopoverService);
12523
- this.mlResolver = inject(AXTranslationService);
13281
+ this.translation = inject(AXTranslationService);
12524
13282
  this.formatService = inject(AXFormatService);
13283
+ this.entityService = inject(AXPEntityService);
13284
+ this.queryExecutor = inject(AXPQueryExecutor);
12525
13285
  //#endregion
12526
- //#region ---- View Children ----
13286
+ //#region ---- View Children ----
12527
13287
  this.moreButton = viewChild('moreButton', ...(ngDevMode ? [{ debugName: "moreButton" }] : /* istanbul ignore next */ []));
13288
+ this.lazyTrigger = viewChild('lazyTrigger', ...(ngDevMode ? [{ debugName: "lazyTrigger" }] : /* istanbul ignore next */ []));
12528
13289
  this.morePopover = viewChild('morePopover', ...(ngDevMode ? [{ debugName: "morePopover" }] : /* istanbul ignore next */ []));
12529
13290
  //#endregion
12530
- //#region ---- Properties ----
13291
+ //#region ---- Properties ----
12531
13292
  this.host = inject(ElementRef);
12532
13293
  this.valueField = this.options['valueField'] ?? 'id';
12533
13294
  this.textField = this.options['textField'] ?? 'title';
12534
- this.entity = this.options['entity'] ?? 'title';
13295
+ this.entity = this.options['entity'] ?? '';
12535
13296
  this.columnName = this.options['columnName'] ?? 'title';
12536
13297
  this.maxVisible = this.options['maxVisible'] ?? 2;
12537
13298
  this.displayFormat = computed(() => {
12538
13299
  const template = this.options['displayFormat'];
12539
13300
  return template ? template.replace(/\{/g, '{{').replace(/\}/g, '}}') : undefined;
12540
13301
  }, ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
12541
- this.displayField = computed(() => {
12542
- return this.textField ?? 'title';
12543
- }, ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
13302
+ this.displayField = computed(() => this.textField ?? 'title', ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
13303
+ this.columnResolve = computed(() => this.options['columnResolve'], ...(ngDevMode ? [{ debugName: "columnResolve" }] : /* istanbul ignore next */ []));
13304
+ this.resolveStrategy = computed(() => {
13305
+ return this.columnResolve()?.strategy ?? 'hydrated';
13306
+ }, ...(ngDevMode ? [{ debugName: "resolveStrategy" }] : /* istanbul ignore next */ []));
13307
+ this.isHydratedStrategy = computed(() => this.resolveStrategy() === 'hydrated', ...(ngDevMode ? [{ debugName: "isHydratedStrategy" }] : /* istanbul ignore next */ []));
12544
13308
  //#endregion
12545
- //#region ---- Signals ----
13309
+ //#region ---- Signals ----
12546
13310
  this.isMorePopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMorePopoverOpen" }] : /* istanbul ignore next */ []));
12547
13311
  this.selectedItemIndex = signal(-1, ...(ngDevMode ? [{ debugName: "selectedItemIndex" }] : /* istanbul ignore next */ []));
13312
+ this.resolvedPopoverItems = signal([], ...(ngDevMode ? [{ debugName: "resolvedPopoverItems" }] : /* istanbul ignore next */ []));
13313
+ this.resolveStatus = signal('idle', ...(ngDevMode ? [{ debugName: "resolveStatus" }] : /* istanbul ignore next */ []));
13314
+ this.resolveError = signal(null, ...(ngDevMode ? [{ debugName: "resolveError" }] : /* istanbul ignore next */ []));
13315
+ this.summaryLabel = signal('', ...(ngDevMode ? [{ debugName: "summaryLabel" }] : /* istanbul ignore next */ []));
13316
+ this.popoverHeader = signal('', ...(ngDevMode ? [{ debugName: "popoverHeader" }] : /* istanbul ignore next */ []));
12548
13317
  //#endregion
12549
- //#region ---- Computed Properties ----
13318
+ //#region ---- Computed ----
12550
13319
  this.displayItems = computed(() => isNil(this.rawValue)
12551
13320
  ? []
12552
13321
  : castArray(this.rawValue)
@@ -12557,34 +13326,127 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12557
13326
  const items = this.allItems();
12558
13327
  return items.slice(0, this.maxVisible);
12559
13328
  }, ...(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 */ []));
13329
+ this.hasMoreItems = computed(() => this.allItems().length > this.maxVisible, ...(ngDevMode ? [{ debugName: "hasMoreItems" }] : /* istanbul ignore next */ []));
13330
+ this.idsFromRow = computed(() => {
13331
+ const cr = this.columnResolve();
13332
+ const path = cr?.idsPath;
13333
+ const source = path ? get(this.rowData, path) : this.rawValue;
13334
+ if (isNil(source)) {
13335
+ return [];
13336
+ }
13337
+ return castArray(source)
13338
+ .map((x) => (typeof x === 'object' && x != null ? get(x, this.valueField) : x))
13339
+ .filter((id) => id != null && id !== '');
13340
+ }, ...(ngDevMode ? [{ debugName: "idsFromRow" }] : /* istanbul ignore next */ []));
13341
+ this.displayCount = computed(() => {
13342
+ const cr = this.columnResolve();
13343
+ const countPath = cr?.countFieldPath;
13344
+ if (countPath) {
13345
+ const v = get(this.rowData, countPath);
13346
+ if (typeof v === 'number' && !Number.isNaN(v)) {
13347
+ return v;
13348
+ }
13349
+ if (typeof v === 'string' && v !== '') {
13350
+ const n = Number(v);
13351
+ return Number.isNaN(n) ? 0 : n;
13352
+ }
13353
+ return 0;
13354
+ }
13355
+ if (this.resolveStrategy() === 'idsWithCount') {
13356
+ return this.idsFromRow().length;
13357
+ }
13358
+ if (this.resolveStrategy() === 'countOnly') {
13359
+ return 0;
13360
+ }
13361
+ return 0;
13362
+ }, ...(ngDevMode ? [{ debugName: "displayCount" }] : /* istanbul ignore next */ []));
13363
+ this.popoverListItems = computed(() => {
13364
+ if (this.isHydratedStrategy()) {
13365
+ return this.allItems();
13366
+ }
13367
+ return this.resolvedPopoverItems();
13368
+ }, ...(ngDevMode ? [{ debugName: "popoverListItems" }] : /* istanbul ignore next */ []));
13369
+ //#endregion
13370
+ //#region ---- Constructor effects ----
13371
+ /** Avoid resetting lazy cache on every CD when `rowData` is a new object reference for the same row. */
13372
+ this.previousLazyRowId = undefined;
13373
+ effect(() => {
13374
+ const id = this.rowData?.['id'];
13375
+ const key = id != null && id !== '' ? String(id) : undefined;
13376
+ if (key === this.previousLazyRowId) {
13377
+ return;
13378
+ }
13379
+ this.previousLazyRowId = key;
13380
+ this.resetLazyState();
13381
+ });
13382
+ effect(() => {
13383
+ if (!this.isHydratedStrategy()) {
13384
+ const count = this.displayCount();
13385
+ void this.refreshSummaryLabel(count);
13386
+ }
13387
+ });
13388
+ effect(() => {
13389
+ const hydrated = this.isHydratedStrategy();
13390
+ const count = hydrated
13391
+ ? this.allItems().length
13392
+ : this.resolveStatus() === 'ready'
13393
+ ? this.resolvedPopoverItems().length
13394
+ : this.displayCount();
13395
+ void this.refreshPopoverHeader(count);
13396
+ });
12566
13397
  }
12567
13398
  //#endregion
12568
- //#region ---- Public Methods ----
13399
+ //#region ---- Public methods ----
12569
13400
  showMoreItems() {
12570
13401
  this.entityDetailPopoverService.hide();
12571
- this.openMorePopover();
13402
+ this.openPopoverFromRef(this.moreButton());
13403
+ }
13404
+ openLazyPopover() {
13405
+ this.entityDetailPopoverService.hide();
13406
+ this.openPopoverFromRef(this.lazyTrigger());
13407
+ if (!this.isHydratedStrategy()) {
13408
+ void this.ensureLazyItemsLoaded();
13409
+ }
12572
13410
  }
12573
- onMorePopoverOpenChange(event) {
12574
- this.isMorePopoverOpen.set(event);
13411
+ onPopoverOpenChange(event) {
13412
+ const open = this.coercePopoverOpenEvent(event);
13413
+ this.isMorePopoverOpen.set(open);
13414
+ if (open && !this.isHydratedStrategy()) {
13415
+ void this.ensureLazyItemsLoaded();
13416
+ }
12575
13417
  }
12576
13418
  async showItemDetail(item, index) {
12577
13419
  if (this.options['disableDetailPopover']) {
12578
13420
  return;
12579
13421
  }
12580
- const columnData = this.rowData[this.columnName];
12581
- const id = Array.isArray(columnData) ? columnData[index] : columnData;
12582
13422
  this.selectedItemIndex.set(index);
12583
13423
  this.closeMorePopover();
12584
- // Show entity detail popover using the service
13424
+ // Prefer the row's FK / stored value (columnName) first. List columns often use options.dataPath so
13425
+ // rawValue (and thus `item`) is only display text (e.g. manager.person.fullName) while managerId holds the real id.
13426
+ const columnData = this.rowData?.[this.columnName];
13427
+ let id;
13428
+ if (Array.isArray(columnData)) {
13429
+ const cell = columnData[index];
13430
+ id =
13431
+ typeof cell === 'object' && cell != null
13432
+ ? get(cell, this.valueField)
13433
+ : cell;
13434
+ }
13435
+ else if (!isNil(columnData) && columnData !== '') {
13436
+ id =
13437
+ typeof columnData === 'object' && columnData != null
13438
+ ? get(columnData, this.valueField)
13439
+ : columnData;
13440
+ }
13441
+ if (isNil(id) || id === '') {
13442
+ id = get(item, this.valueField);
13443
+ }
13444
+ if (isNil(id) || id === '') {
13445
+ return;
13446
+ }
12585
13447
  await this.entityDetailPopoverService.show(this.host, {
12586
13448
  entity: this.entity,
12587
- id: id,
13449
+ id,
12588
13450
  textField: this.textField,
12589
13451
  valueField: this.valueField,
12590
13452
  item,
@@ -12596,36 +13458,25 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12596
13458
  }
12597
13459
  const items = this.allItems();
12598
13460
  if (index < items.length) {
12599
- const item = items[index];
12600
- this.showItemDetail(item, index);
13461
+ this.showItemDetail(items[index], index);
12601
13462
  }
12602
13463
  }
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);
13464
+ handleResolvedItemClick(index) {
13465
+ if (this.options['disableDetailPopover']) {
13466
+ return;
12610
13467
  }
12611
- }
12612
- closeMorePopover() {
12613
- if (this.morePopover()) {
12614
- this.morePopover().close();
12615
- this.isMorePopoverOpen.set(false);
13468
+ const items = this.resolvedPopoverItems();
13469
+ if (index < items.length) {
13470
+ this.showItemDetail(items[index], index);
12616
13471
  }
12617
13472
  }
12618
- extractItem(item) {
12619
- if (isNil(item)) {
12620
- return null;
13473
+ handlePopoverItemClick(index) {
13474
+ if (this.isHydratedStrategy()) {
13475
+ this.handleItemClick(index);
12621
13476
  }
12622
- if (typeof item === 'object') {
12623
- return item;
13477
+ else {
13478
+ this.handleResolvedItemClick(index);
12624
13479
  }
12625
- return {
12626
- [this.valueField]: item,
12627
- [this.textField]: item,
12628
- };
12629
13480
  }
12630
13481
  getDisplayRaw(item) {
12631
13482
  if (isNil(item)) {
@@ -12643,6 +13494,178 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12643
13494
  }
12644
13495
  return resolved ?? '';
12645
13496
  }
13497
+ //#endregion
13498
+ //#region ---- Private methods ----
13499
+ async refreshSummaryLabel(count) {
13500
+ const text = await this.translation.translateAsync('@general:widgets.lookup.column.items-count', {
13501
+ params: { count: String(count) },
13502
+ });
13503
+ this.summaryLabel.set(text);
13504
+ }
13505
+ async refreshPopoverHeader(count) {
13506
+ const text = await this.translation.translateAsync('@general:widgets.lookup.column.popover-title', {
13507
+ params: { count: String(count) },
13508
+ });
13509
+ this.popoverHeader.set(text);
13510
+ }
13511
+ resetLazyState() {
13512
+ this.resolvedPopoverItems.set([]);
13513
+ this.resolveStatus.set('idle');
13514
+ this.resolveError.set(null);
13515
+ }
13516
+ openPopoverFromRef(ref) {
13517
+ const popover = this.morePopover();
13518
+ if (popover && ref) {
13519
+ popover.target = ref.nativeElement;
13520
+ popover.open();
13521
+ this.isMorePopoverOpen.set(true);
13522
+ }
13523
+ }
13524
+ closeMorePopover() {
13525
+ const popover = this.morePopover();
13526
+ if (popover) {
13527
+ popover.close();
13528
+ this.isMorePopoverOpen.set(false);
13529
+ }
13530
+ }
13531
+ async ensureLazyItemsLoaded() {
13532
+ if (this.resolveStatus() === 'loading') {
13533
+ return;
13534
+ }
13535
+ const strategy = this.resolveStrategy();
13536
+ if (strategy === 'idsWithCount') {
13537
+ await this.loadByIds();
13538
+ return;
13539
+ }
13540
+ if (strategy === 'countOnly') {
13541
+ await this.loadByNamedQuery();
13542
+ }
13543
+ }
13544
+ /**
13545
+ * ax-popover may emit a boolean, CustomEvent with detail, or a DOM Event depending on version.
13546
+ */
13547
+ coercePopoverOpenEvent(event) {
13548
+ if (typeof event === 'boolean') {
13549
+ return event;
13550
+ }
13551
+ if (event && typeof event === 'object') {
13552
+ const o = event;
13553
+ if ('detail' in o) {
13554
+ return Boolean(o['detail']);
13555
+ }
13556
+ if ('open' in o) {
13557
+ return Boolean(o['open']);
13558
+ }
13559
+ }
13560
+ return false;
13561
+ }
13562
+ async loadByIds() {
13563
+ const ids = this.idsFromRow();
13564
+ if (!ids.length) {
13565
+ this.resolvedPopoverItems.set([]);
13566
+ this.resolveStatus.set('ready');
13567
+ return;
13568
+ }
13569
+ if (!this.entity?.includes('.')) {
13570
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
13571
+ this.resolveError.set(msg);
13572
+ this.resolveStatus.set('error');
13573
+ return;
13574
+ }
13575
+ this.resolveStatus.set('loading');
13576
+ this.resolveError.set(null);
13577
+ try {
13578
+ const accessor = this.entityService.withEntity(this.entity).data();
13579
+ const results = await Promise.all(ids.map((id) => accessor.byKey(id)));
13580
+ this.resolvedPopoverItems.set(results.filter((item) => item != null));
13581
+ this.resolveStatus.set('ready');
13582
+ }
13583
+ catch {
13584
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
13585
+ this.resolveError.set(msg);
13586
+ this.resolveStatus.set('error');
13587
+ this.resolvedPopoverItems.set([]);
13588
+ }
13589
+ }
13590
+ async loadByNamedQuery() {
13591
+ const cr = this.columnResolve();
13592
+ const key = cr?.queryKey;
13593
+ if (!key?.trim()) {
13594
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
13595
+ this.resolveError.set(msg);
13596
+ this.resolveStatus.set('error');
13597
+ return;
13598
+ }
13599
+ this.resolveStatus.set('loading');
13600
+ this.resolveError.set(null);
13601
+ try {
13602
+ const input = this.buildNamedQueryInput();
13603
+ const result = await this.queryExecutor.fetch(key, input);
13604
+ const items = this.extractItemsFromQueryResult(result);
13605
+ this.resolvedPopoverItems.set(items);
13606
+ this.resolveStatus.set('ready');
13607
+ }
13608
+ catch {
13609
+ const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
13610
+ this.resolveError.set(msg);
13611
+ this.resolveStatus.set('error');
13612
+ this.resolvedPopoverItems.set([]);
13613
+ }
13614
+ }
13615
+ buildNamedQueryInput() {
13616
+ const map = this.parseQueryParamsMap();
13617
+ const row = this.rowData ?? {};
13618
+ if (!map) {
13619
+ return {};
13620
+ }
13621
+ const out = {};
13622
+ for (const [param, path] of Object.entries(map)) {
13623
+ out[param] = get(row, path);
13624
+ }
13625
+ return out;
13626
+ }
13627
+ parseQueryParamsMap() {
13628
+ const raw = this.columnResolve()?.queryParams;
13629
+ if (raw == null) {
13630
+ return null;
13631
+ }
13632
+ if (typeof raw === 'string') {
13633
+ try {
13634
+ const parsed = JSON.parse(raw);
13635
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
13636
+ ? parsed
13637
+ : null;
13638
+ }
13639
+ catch {
13640
+ return null;
13641
+ }
13642
+ }
13643
+ return raw;
13644
+ }
13645
+ extractItemsFromQueryResult(result) {
13646
+ if (result == null) {
13647
+ return [];
13648
+ }
13649
+ if (Array.isArray(result)) {
13650
+ return result;
13651
+ }
13652
+ const cr = this.columnResolve();
13653
+ const path = cr?.queryResultItemsPath ?? 'items';
13654
+ const nested = get(result, path);
13655
+ return Array.isArray(nested) ? nested : [];
13656
+ }
13657
+ extractItem(item) {
13658
+ if (isNil(item)) {
13659
+ return null;
13660
+ }
13661
+ if (typeof item === 'object') {
13662
+ return item;
13663
+ }
13664
+ return {
13665
+ [this.valueField]: item,
13666
+ [this.textField]: item,
13667
+ };
13668
+ }
12646
13669
  resolveDisplayValue(item) {
12647
13670
  if (isNil(item)) {
12648
13671
  return '';
@@ -12657,18 +13680,18 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
12657
13680
  if (!isNil(rawDisplayValue)) {
12658
13681
  return this.resolveDisplayValue(rawDisplayValue);
12659
13682
  }
12660
- if (this.mlResolver.isValidMultiLanguageObject(item)) {
12661
- return this.mlResolver.resolve(item);
13683
+ if (this.translation.isValidMultiLanguageObject(item)) {
13684
+ return this.translation.resolve(item);
12662
13685
  }
12663
13686
  return '';
12664
13687
  }
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 }); }
13688
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13689
+ 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
13690
  }
12668
13691
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, decorators: [{
12669
13692
  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 }] }] } });
13693
+ 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" }]
13694
+ }], 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
13695
 
12673
13696
  var lookupWidgetColumn_component = /*#__PURE__*/Object.freeze({
12674
13697
  __proto__: null,
@@ -12730,6 +13753,100 @@ const AXPLookupWidget = {
12730
13753
  },
12731
13754
  visible: true,
12732
13755
  },
13756
+ {
13757
+ name: 'columnResolveStrategy',
13758
+ title: 'Column resolve strategy',
13759
+ group: AXP_DATA_PROPERTY_GROUP,
13760
+ schema: {
13761
+ dataType: 'string',
13762
+ defaultValue: 'hydrated',
13763
+ interface: {
13764
+ name: 'columnResolveStrategy',
13765
+ path: 'options.columnResolve.strategy',
13766
+ type: AXPWidgetsCatalog.select,
13767
+ valueTransforms: objectKeyValueTransforms('id'),
13768
+ options: {
13769
+ dataSource: [
13770
+ { id: 'hydrated', title: 'Hydrated (titles on row)' },
13771
+ { id: 'idsWithCount', title: 'Ids + count on row (load titles on open)' },
13772
+ { id: 'countOnly', title: 'Count on row (named query on open)' },
13773
+ ],
13774
+ },
13775
+ },
13776
+ },
13777
+ visible: true,
13778
+ },
13779
+ {
13780
+ name: 'columnResolveCountFieldPath',
13781
+ title: 'Column resolve — count field path',
13782
+ group: AXP_DATA_PROPERTY_GROUP,
13783
+ schema: {
13784
+ dataType: 'string',
13785
+ interface: {
13786
+ name: 'columnResolveCountFieldPath',
13787
+ path: 'options.columnResolve.countFieldPath',
13788
+ type: AXPWidgetsCatalog.text,
13789
+ },
13790
+ },
13791
+ visible: true,
13792
+ },
13793
+ {
13794
+ name: 'columnResolveIdsPath',
13795
+ title: 'Column resolve — ids path',
13796
+ group: AXP_DATA_PROPERTY_GROUP,
13797
+ schema: {
13798
+ dataType: 'string',
13799
+ interface: {
13800
+ name: 'columnResolveIdsPath',
13801
+ path: 'options.columnResolve.idsPath',
13802
+ type: AXPWidgetsCatalog.text,
13803
+ },
13804
+ },
13805
+ visible: true,
13806
+ },
13807
+ {
13808
+ name: 'columnResolveQueryKey',
13809
+ title: 'Column resolve — query key (countOnly)',
13810
+ group: AXP_DATA_PROPERTY_GROUP,
13811
+ schema: {
13812
+ dataType: 'string',
13813
+ interface: {
13814
+ name: 'columnResolveQueryKey',
13815
+ path: 'options.columnResolve.queryKey',
13816
+ type: AXPWidgetsCatalog.text,
13817
+ },
13818
+ },
13819
+ visible: true,
13820
+ },
13821
+ {
13822
+ name: 'columnResolveQueryParams',
13823
+ title: 'Column resolve — query params (JSON: param → row path)',
13824
+ group: AXP_DATA_PROPERTY_GROUP,
13825
+ schema: {
13826
+ dataType: 'string',
13827
+ interface: {
13828
+ name: 'columnResolveQueryParams',
13829
+ path: 'options.columnResolve.queryParams',
13830
+ type: AXPWidgetsCatalog.text,
13831
+ },
13832
+ },
13833
+ visible: true,
13834
+ },
13835
+ {
13836
+ name: 'columnResolveQueryResultItemsPath',
13837
+ title: 'Column resolve — query result items path',
13838
+ group: AXP_DATA_PROPERTY_GROUP,
13839
+ schema: {
13840
+ dataType: 'string',
13841
+ defaultValue: 'items',
13842
+ interface: {
13843
+ name: 'columnResolveQueryResultItemsPath',
13844
+ path: 'options.columnResolve.queryResultItemsPath',
13845
+ type: AXPWidgetsCatalog.text,
13846
+ },
13847
+ },
13848
+ visible: true,
13849
+ },
12733
13850
  ],
12734
13851
  components: {
12735
13852
  view: {
@@ -12747,6 +13864,8 @@ const AXPLookupWidget = {
12747
13864
  },
12748
13865
  };
12749
13866
 
13867
+ //#endregion
13868
+
12750
13869
  /**
12751
13870
  * Source type
12752
13871
  */
@@ -15336,13 +16455,18 @@ class AXPDetailsViewBadgeStatusService {
15336
16455
  if (!entityDefinition) {
15337
16456
  return null;
15338
16457
  }
15339
- const statusPlugin = entityDefinition.plugins?.find((p) => p.name === 'status');
15340
- if (!statusPlugin?.options) {
16458
+ const statusPlugins = (entityDefinition.plugins ?? []).filter((p) => p.name === 'status');
16459
+ if (statusPlugins.length === 0) {
15341
16460
  return null;
15342
16461
  }
15343
- const statusOptions = statusPlugin.options;
16462
+ const explicitPrimary = statusPlugins.find((p) => p.options?.isPrimary === true);
16463
+ const primaryPlugin = explicitPrimary ??
16464
+ statusPlugins.find((p) => p.options?.isPrimary !== false) ??
16465
+ statusPlugins[0];
16466
+ const statusOptions = primaryPlugin.options;
15344
16467
  const statusField = statusOptions.field ?? 'statusId';
15345
16468
  const statusDefinitionKey = statusOptions.definition;
16469
+ const pluginReadonly = statusOptions.readonly === true;
15346
16470
  let definitionKey = typeof statusDefinitionKey === 'string' ? statusDefinitionKey : undefined;
15347
16471
  if (!definitionKey) {
15348
16472
  const statusProperty = entityDefinition.properties?.find((p) => p.name === statusField);
@@ -15363,7 +16487,7 @@ class AXPDetailsViewBadgeStatusService {
15363
16487
  definitionKey,
15364
16488
  value: String(value),
15365
16489
  dataPath: statusField,
15366
- readonly: currentPage?.isReadonly ?? false,
16490
+ readonly: pluginReadonly || (currentPage?.isReadonly ?? false),
15367
16491
  };
15368
16492
  }
15369
16493
  catch (error) {
@@ -15546,34 +16670,35 @@ class AXPBaseRelatedEntityConverter {
15546
16670
  groups,
15547
16671
  };
15548
16672
  }
15549
- async createGridLayoutStructure(singleInterface, helpers, evaluateExpressions, includeProperties) {
15550
- return await this.createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions, includeProperties);
16673
+ createGridLayoutStructure(singleInterface, helpers, includeProperties) {
16674
+ return this.createGridLayoutStructureInternal(singleInterface, helpers, includeProperties);
15551
16675
  }
15552
- //#region ---- Visible Evaluation Helpers ----
15553
- async getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions, includeProperties) {
16676
+ //#region ---- Property visibility (layout) ----
16677
+ /**
16678
+ * Chooses which properties appear in the related-entity grid layout.
16679
+ * Boolean `schema.visible === false` excludes a property. String templates are kept in the layout
16680
+ * and passed on `form-field.options.visible`; `AXPWidgetRendererDirective` uses
16681
+ * `AXPExpressionEvaluatorService` in `updateVisibility()` to evaluate them when context updates.
16682
+ */
16683
+ getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties) {
15554
16684
  let properties = helpers.getPropertyByGroupId(sectionId) ?? [];
15555
16685
  // Filter by includeProperties if provided
15556
16686
  if (includeProperties && includeProperties.length > 0) {
15557
16687
  const includeSet = new Set(includeProperties);
15558
16688
  properties = properties.filter((p) => includeSet.has(p.name));
15559
16689
  }
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
- }
16690
+ const evaluated = properties.map((property) => {
16691
+ const visible = property?.schema?.visible;
16692
+ if (typeof visible === 'string') {
16693
+ // Include in layout; runtime evaluation via `AXPExpressionEvaluatorService` (widget renderer).
16694
+ return { property, visible: true };
15570
16695
  }
15571
16696
  return { property, visible: visible ?? true };
15572
- }));
16697
+ });
15573
16698
  return evaluated.filter((x) => x.visible !== false).map((x) => x.property);
15574
16699
  }
15575
- async createPropertyGrid(sectionId, helpers, evaluateExpressions, includeProperties) {
15576
- const visibleProperties = await this.getVisiblePropertiesByGroupId(sectionId, helpers, evaluateExpressions, includeProperties);
16700
+ createPropertyGrid(sectionId, helpers, includeProperties) {
16701
+ const visibleProperties = this.getVisiblePropertiesByGroupId(sectionId, helpers, includeProperties);
15577
16702
  return {
15578
16703
  type: 'grid-layout',
15579
16704
  mode: 'edit',
@@ -15588,6 +16713,15 @@ class AXPBaseRelatedEntityConverter {
15588
16713
  },
15589
16714
  children: visibleProperties.map((p) => {
15590
16715
  const layout = helpers.getPropertyLayout(p.name);
16716
+ const sv = p.schema?.visible;
16717
+ let formFieldVisible;
16718
+ if (typeof sv === 'string') {
16719
+ // Evaluated by `AXPExpressionEvaluatorService` in `AXPWidgetRendererDirective.updateVisibility()`.
16720
+ formFieldVisible = sv;
16721
+ }
16722
+ else if (sv === false) {
16723
+ formFieldVisible = false;
16724
+ }
15591
16725
  return {
15592
16726
  type: 'grid-item-layout',
15593
16727
  name: p.name,
@@ -15602,6 +16736,8 @@ class AXPBaseRelatedEntityConverter {
15602
16736
  options: {
15603
16737
  label: p.title,
15604
16738
  showLabel: layout?.label?.visible ?? true,
16739
+ ...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
16740
+ ...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
15605
16741
  },
15606
16742
  children: [
15607
16743
  {
@@ -15621,13 +16757,15 @@ class AXPBaseRelatedEntityConverter {
15621
16757
  }),
15622
16758
  };
15623
16759
  }
15624
- async createGridLayoutStructureInternal(singleInterface, helpers, evaluateExpressions, includeProperties) {
16760
+ createGridLayoutStructureInternal(singleInterface, helpers, includeProperties) {
15625
16761
  // 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);
16762
+ const sectionsWithProperties = (singleInterface?.sections ?? []).map((s) => {
16763
+ const visibleProperties = this.getVisiblePropertiesByGroupId(s.id, helpers, includeProperties);
15628
16764
  return { section: s, hasProperties: visibleProperties.length > 0 };
15629
- }));
15630
- const validSections = sectionsWithProperties.filter((item) => item.hasProperties).map((item) => item.section);
16765
+ });
16766
+ const validSections = sectionsWithProperties
16767
+ .filter((item) => item.hasProperties)
16768
+ .map((item) => item.section);
15631
16769
  return {
15632
16770
  type: 'grid-layout',
15633
16771
  options: {
@@ -15639,7 +16777,7 @@ class AXPBaseRelatedEntityConverter {
15639
16777
  },
15640
16778
  },
15641
16779
  },
15642
- children: await Promise.all(validSections.map(async (s) => ({
16780
+ children: validSections.map((s) => ({
15643
16781
  type: 'grid-item-layout',
15644
16782
  name: s.id,
15645
16783
  options: {
@@ -15653,11 +16791,12 @@ class AXPBaseRelatedEntityConverter {
15653
16791
  options: {
15654
16792
  title: helpers.getGroupById(s.id)?.title ?? '',
15655
16793
  collapsible: true,
16794
+ showTitle: s.layout?.label?.visible ?? true,
15656
16795
  },
15657
- children: [await this.createPropertyGrid(s.id, helpers, evaluateExpressions, includeProperties)],
16796
+ children: [this.createPropertyGrid(s.id, helpers, includeProperties)],
15658
16797
  },
15659
16798
  ],
15660
- }))),
16799
+ })),
15661
16800
  };
15662
16801
  }
15663
16802
  }
@@ -15860,7 +16999,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
15860
16999
  execute: this.createExecuteFunction(entityDef, actions, evaluateExpressions, context),
15861
17000
  actions: await this.buildEvaluatedActions(actions, evaluateExpressions),
15862
17001
  // tabs: [...tabDetailTabs, ...tabListTabs],
15863
- content: [await this.createGridLayoutStructure(helpers.singleInterface, helpers, evaluateExpressions, relatedEntity.properties)],
17002
+ content: [this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties)],
15864
17003
  };
15865
17004
  }
15866
17005
  //#region ---- Utility Methods ----
@@ -16178,16 +17317,34 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
16178
17317
  evaluatedOptions = action.options;
16179
17318
  }
16180
17319
  }
16181
- const actionData = action.scope == AXPEntityCommandScope.Selected
17320
+ let actionData = action.scope == AXPEntityCommandScope.Selected
16182
17321
  ? executeContext
16183
17322
  : evaluatedOptions?.['process']?.data || null;
16184
- //TODO: This is a temporary solution to exclude properties from the related entity.
16185
- // We need to find a better way to handle this.
16186
- const excludeForCreateOrUpdate = (commandName === 'Entity:Create' || commandName === 'Entity:Update') &&
16187
- relatedEntity.excludeProperties?.length;
16188
- const mergedOptions = excludeForCreateOrUpdate
16189
- ? { ...evaluatedOptions, excludeProperties: relatedEntity.excludeProperties }
16190
- : evaluatedOptions;
17323
+ const foreignKeyField = relatedEntity.persistence?.foreignKeyField;
17324
+ if (commandName === 'Entity:Create' && foreignKeyField && context.context) {
17325
+ const parentId = get(context.context, 'id');
17326
+ actionData = {
17327
+ ...(typeof actionData === 'object' && actionData !== null ? actionData : {}),
17328
+ ...(parentId !== undefined && parentId !== null && parentId !== ''
17329
+ ? { [foreignKeyField]: parentId }
17330
+ : {}),
17331
+ };
17332
+ }
17333
+ const createExcludes = [
17334
+ ...new Set([
17335
+ ...(relatedEntity.excludeProperties ?? []),
17336
+ ...(commandName === 'Entity:Create' && foreignKeyField ? [foreignKeyField] : []),
17337
+ ]),
17338
+ ];
17339
+ const excludeForCreate = commandName === 'Entity:Create' && createExcludes.length > 0;
17340
+ const excludeForUpdate = commandName === 'Entity:Update' &&
17341
+ relatedEntity.excludeProperties &&
17342
+ relatedEntity.excludeProperties.length > 0;
17343
+ const mergedOptions = excludeForCreate
17344
+ ? { ...evaluatedOptions, excludeProperties: createExcludes }
17345
+ : excludeForUpdate
17346
+ ? { ...evaluatedOptions, excludeProperties: relatedEntity.excludeProperties }
17347
+ : evaluatedOptions;
16191
17348
  if (context.commandService.exists(commandName)) {
16192
17349
  // check options for evaluation
16193
17350
  await context.commandService.execute(commandName, {
@@ -16252,6 +17409,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
16252
17409
  includeColumns,
16253
17410
  relatedTableColumns,
16254
17411
  customFilterDefinitions: relatedEntity.customFilterDefinitions,
17412
+ excludeProperties: relatedEntity.excludeProperties,
16255
17413
  },
16256
17414
  },
16257
17415
  ],
@@ -16310,7 +17468,7 @@ class AXPTabDetailsConverter extends AXPBaseRelatedEntityConverter {
16310
17468
  title: relatedEntity.title ?? entityDef?.title ?? '',
16311
17469
  icon: relatedEntity.icon || entityDef.icon,
16312
17470
  content: [
16313
- await this.createGridLayoutStructure(helpers.singleInterface, helpers, undefined, relatedEntity.properties),
17471
+ this.createGridLayoutStructure(helpers.singleInterface, helpers, relatedEntity.properties),
16314
17472
  ],
16315
17473
  };
16316
17474
  }
@@ -16370,6 +17528,7 @@ class AXPTabListConverter extends AXPBaseRelatedEntityConverter {
16370
17528
  includeColumns,
16371
17529
  relatedTableColumns,
16372
17530
  customFilterDefinitions: relatedEntity.customFilterDefinitions,
17531
+ excludeProperties: relatedEntity.excludeProperties,
16373
17532
  },
16374
17533
  },
16375
17534
  ],
@@ -16866,20 +18025,18 @@ class AXPMainEntityContentBuilder {
16866
18025
  const visibleInRelated = isFromRelated ? !!p.__layout : false;
16867
18026
  return visibleInMain || visibleInRelated;
16868
18027
  });
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
- }
18028
+ // String `schema.visible` is not evaluated here (no `dependencies.expressionEvaluator` call).
18029
+ // The template is placed on `form-field.options.visible` below; at render time
18030
+ // `AXPWidgetRendererDirective` injects `AXPExpressionEvaluatorService` and evaluates it in
18031
+ // `updateVisibility()` (and on context changes via `hasVisibilityDependency`). Evaluating once
18032
+ // at build time would freeze visibility and break detail/edit when data changes.
18033
+ const visibilityEvaluated = inView.map((p) => {
18034
+ const v = p.schema?.visible;
18035
+ if (typeof v === 'string') {
18036
+ return { property: p, resolvedVisible: true };
16879
18037
  }
16880
- const resolved = visible !== false;
16881
- return { property: p, resolvedVisible: resolved };
16882
- }));
18038
+ return { property: p, resolvedVisible: v !== false };
18039
+ });
16883
18040
  const visibleProperties = visibilityEvaluated
16884
18041
  .filter((x) => x.resolvedVisible)
16885
18042
  .map((x) => x.property);
@@ -16903,6 +18060,7 @@ class AXPMainEntityContentBuilder {
16903
18060
  collapsible: true,
16904
18061
  isOpen: !s.collapsed,
16905
18062
  look: 'card',
18063
+ showTitle: s.layout?.label?.visible !== false,
16906
18064
  },
16907
18065
  children: [
16908
18066
  {
@@ -16927,8 +18085,9 @@ class AXPMainEntityContentBuilder {
16927
18085
  const hasOwnDisabled = p.schema.interface?.options?.disabled !== undefined;
16928
18086
  let formFieldVisible;
16929
18087
  const sv = p.schema?.visible;
18088
+ // Template strings are evaluated by `AXPExpressionEvaluatorService` in the widget renderer, not here.
16930
18089
  if (typeof sv === 'string') {
16931
- formFieldVisible = true;
18090
+ formFieldVisible = sv;
16932
18091
  }
16933
18092
  else if (sv === false) {
16934
18093
  formFieldVisible = false;
@@ -16945,6 +18104,7 @@ class AXPMainEntityContentBuilder {
16945
18104
  rowStart: layout?.positions?.lg?.rowStart,
16946
18105
  rowEnd: layout?.positions?.lg?.rowEnd,
16947
18106
  ...(formFieldVisible !== undefined ? { visible: formFieldVisible } : {}),
18107
+ ...(hintFormFieldOptionsFromDescription(p.description) ?? {}),
16948
18108
  },
16949
18109
  children: [
16950
18110
  {
@@ -17942,6 +19102,7 @@ const AXPCrudModifier = {
17942
19102
  if (!command?.create) {
17943
19103
  command.create = {
17944
19104
  execute: async (data) => {
19105
+ console.log('create', ctx.module.get() + '.' + ctx.name.get(), data);
17945
19106
  const res = await dataService.insertOne(data);
17946
19107
  return { id: res };
17947
19108
  },
@@ -17957,6 +19118,7 @@ const AXPCrudModifier = {
17957
19118
  if (!command?.update) {
17958
19119
  command.update = {
17959
19120
  execute: async (data) => {
19121
+ console.log('update', ctx.module.get() + '.' + ctx.name.get(), data);
17960
19122
  return await dataService.updateOne(data.id, data);
17961
19123
  },
17962
19124
  };
@@ -17971,7 +19133,6 @@ const AXPCrudModifier = {
17971
19133
  queries.byKey = {
17972
19134
  execute: async (id) => {
17973
19135
  const data = await dataService.getOne(id);
17974
- // debugger;
17975
19136
  return data;
17976
19137
  },
17977
19138
  type: AXPEntityQueryType.Single,
@@ -17980,6 +19141,7 @@ const AXPCrudModifier = {
17980
19141
  if (!queries?.list) {
17981
19142
  queries.list = {
17982
19143
  execute: async (e) => {
19144
+ console.log('query', ctx.module.get() + '.' + ctx.name.get(), e);
17983
19145
  return await dataService.query(e);
17984
19146
  },
17985
19147
  type: AXPEntityQueryType.List,
@@ -18575,10 +19737,34 @@ class AXPShowListViewAction extends AXPWorkflowAction {
18575
19737
  this.sessionService = inject(AXPSessionService);
18576
19738
  }
18577
19739
  async execute(context) {
18578
- const [moduleName, entityName] = context.getVariable('entity').split(".");
19740
+ const entity = context.getVariable('entity');
19741
+ const [moduleName, entityName] = entity.split('.');
18579
19742
  const newPayload = {
18580
19743
  commands: `/${this.sessionService.application?.name}/m/${moduleName}/e/${entityName}/list`,
18581
19744
  };
19745
+ const conditions = context.getVariable('conditions');
19746
+ if (Array.isArray(conditions) && conditions.length > 0) {
19747
+ const filterQueries = conditions
19748
+ .map((c) => {
19749
+ const field = c.name ?? c.field;
19750
+ if (!field || c.value === undefined || c.value === null || c.value === '') {
19751
+ return null;
19752
+ }
19753
+ return {
19754
+ field,
19755
+ operator: c.operator ?? { type: 'equal' },
19756
+ value: c.value,
19757
+ };
19758
+ })
19759
+ .filter((entry) => entry !== null);
19760
+ if (filterQueries.length > 0) {
19761
+ newPayload.extras = {
19762
+ queryParams: {
19763
+ filters: JSON.stringify(filterQueries),
19764
+ },
19765
+ };
19766
+ }
19767
+ }
18582
19768
  context.setVariable('payload', newPayload);
18583
19769
  this.navigation.execute(context);
18584
19770
  }
@@ -19145,244 +20331,9 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
19145
20331
  AXPGetEntityDetailsQuery: AXPGetEntityDetailsQuery
19146
20332
  });
19147
20333
 
19148
- // #region Master
19149
- function entityMasterCreateAction() {
19150
- return {
19151
- title: '@general:actions.create.title',
19152
- command: {
19153
- name: 'Entity:Create',
19154
- },
19155
- priority: 'primary',
19156
- type: AXPSystemActionType.Create,
19157
- scope: AXPEntityCommandScope.TypeLevel,
19158
- };
19159
- }
19160
- function entityMasterEditAction() {
19161
- return {
19162
- title: '@general:actions.edit.title',
19163
- command: 'Entity:Update',
19164
- priority: 'secondary',
19165
- type: AXPSystemActionType.Update,
19166
- scope: AXPEntityCommandScope.Individual,
19167
- default: true,
19168
- };
19169
- }
19170
- function entityMasterBulkDeleteAction() {
19171
- return {
19172
- title: '@general:actions.delete-items.title',
19173
- command: 'delete-entity',
19174
- priority: 'primary',
19175
- type: AXPSystemActionType.Delete,
19176
- scope: AXPEntityCommandScope.Selected,
19177
- order: 100,
19178
- };
19179
- }
19180
- function entityMasterViewAction() {
19181
- return {
19182
- title: '@general:actions.view.title',
19183
- command: 'open-entity',
19184
- priority: 'secondary',
19185
- type: AXPSystemActionType.View,
19186
- scope: AXPEntityCommandScope.Individual,
19187
- default: true,
19188
- };
19189
- }
19190
- function entityMasterDeleteAction() {
19191
- return {
19192
- title: '@general:actions.delete.title',
19193
- command: 'delete-entity',
19194
- priority: 'secondary',
19195
- type: AXPSystemActionType.Delete,
19196
- scope: AXPEntityCommandScope.Individual,
19197
- order: 100,
19198
- };
19199
- }
19200
- function entityMasterCrudActions(options) {
19201
- const opts = {
19202
- create: true,
19203
- delete: true,
19204
- view: true,
19205
- edit: false,
19206
- ...options,
19207
- };
19208
- const actions = [];
19209
- if (opts.create) {
19210
- actions.push(entityMasterCreateAction());
19211
- }
19212
- if (opts.delete) {
19213
- actions.push(entityMasterBulkDeleteAction());
19214
- actions.push(entityMasterDeleteAction());
19215
- }
19216
- if (opts.view) {
19217
- actions.push(entityMasterViewAction());
19218
- }
19219
- if (opts.edit) {
19220
- actions.push(entityMasterEditAction());
19221
- }
19222
- return actions;
19223
- }
19224
- function entityMasterRecordActions() {
19225
- return [entityMasterDeleteAction()];
19226
- }
19227
- // #endregion
19228
- // #region Details
19229
- function entityDetailsCreateActions(parentId) {
19230
- return {
19231
- title: '@general:actions.create.title',
19232
- command: {
19233
- name: 'Entity:Create',
19234
- options: {
19235
- process: {
19236
- redirect: false,
19237
- canCreateNewOne: true,
19238
- data: {
19239
- [parentId]: '{{context.eval("id")}}',
19240
- },
19241
- },
19242
- },
19243
- },
19244
- priority: 'primary',
19245
- type: AXPSystemActionType.Create,
19246
- scope: AXPEntityCommandScope.TypeLevel,
19247
- };
19248
- }
19249
- function entityDetailsSimpleCondition(fk) {
19250
- return {
19251
- name: fk,
19252
- operator: { type: 'equal' },
19253
- value: '{{context.eval("id")}}',
19254
- };
19255
- }
19256
- function entityDetailsReferenceCondition(type) {
19257
- return [
19258
- {
19259
- name: 'reference.id',
19260
- operator: { type: 'equal' },
19261
- value: '{{context.eval("id")}}',
19262
- },
19263
- {
19264
- name: 'reference.type',
19265
- operator: { type: 'equal' },
19266
- value: type,
19267
- },
19268
- ];
19269
- }
19270
- function entityDetailsEditAction() {
19271
- return {
19272
- title: '@general:actions.edit.title',
19273
- // command: 'quick-modify-entity',
19274
- command: 'Entity:Update',
19275
- priority: 'secondary',
19276
- type: AXPSystemActionType.Update,
19277
- default: true,
19278
- scope: AXPEntityCommandScope.Individual,
19279
- };
19280
- }
19281
- function entityDetailsNewEditAction() {
19282
- return {
19283
- title: 'New Edit',
19284
- command: {
19285
- name: 'Entity:Update',
19286
- },
19287
- priority: 'secondary',
19288
- type: AXPSystemActionType.Update,
19289
- default: true,
19290
- scope: AXPEntityCommandScope.Individual,
19291
- };
19292
- }
19293
- function entityOverrideDetailsViewAction() {
19294
- return {
19295
- title: '@general:actions.view.title',
19296
- command: 'open-entity',
19297
- priority: 'secondary',
19298
- hidden: true,
19299
- type: AXPSystemActionType.View,
19300
- scope: AXPEntityCommandScope.Individual,
19301
- };
19302
- }
19303
- function entityDetailsCrudActions(parentId, options) {
19304
- const opts = {
19305
- create: true,
19306
- delete: true,
19307
- view: true,
19308
- edit: true,
19309
- ...options,
19310
- };
19311
- const actions = [];
19312
- if (opts.create) {
19313
- actions.push(entityDetailsCreateActions(parentId));
19314
- }
19315
- if (opts.edit) {
19316
- actions.push(entityDetailsEditAction());
19317
- }
19318
- if (opts.view) {
19319
- actions.push(entityOverrideDetailsViewAction());
19320
- }
19321
- return actions;
19322
- }
19323
- function entityDetailsReferenceCreateActions(type) {
19324
- return [
19325
- {
19326
- title: '@general:actions.create.title',
19327
- command: {
19328
- name: 'Entity:Create',
19329
- options: {
19330
- process: {
19331
- redirect: false,
19332
- canCreateNewOne: true,
19333
- data: {
19334
- reference: {
19335
- id: '{{context.eval("id")}}',
19336
- type: type,
19337
- },
19338
- },
19339
- },
19340
- },
19341
- },
19342
- priority: 'primary',
19343
- type: AXPSystemActionType.Create,
19344
- scope: AXPEntityCommandScope.TypeLevel,
19345
- },
19346
- entityDetailsEditAction(),
19347
- entityOverrideDetailsViewAction(),
19348
- ];
19349
- }
19350
- /**
19351
- * Computes a diff between two plain objects with array-aware semantics.
19352
- * - For arrays of objects with an id field, computes added/removed by id.
19353
- * - For arrays of primitives or objects without id, uses deep equality.
19354
- * - For scalars/objects, reports oldValue/newValue when changed.
19355
- */
19356
- function detectEntityChanges(oldObj, newObj) {
19357
- return transform(newObj, (result, value, key) => {
19358
- if (!isEqual$1(value, oldObj[key])) {
19359
- const oldValue = oldObj[key];
19360
- if (Array.isArray(value) || Array.isArray(oldValue)) {
19361
- const oldArray = Array.isArray(oldValue) ? oldValue : [];
19362
- const newArray = Array.isArray(value) ? value : [];
19363
- const hasId = newArray.length > 0 && typeof newArray[0] === 'object' && newArray[0] !== null && 'id' in newArray[0];
19364
- if (hasId) {
19365
- const added = newArray.filter((item) => !oldArray.some((oldItem) => oldItem.id === item.id));
19366
- const removed = oldArray.filter((item) => !newArray.some((newItem) => newItem.id === item.id));
19367
- result[key] = { oldValue, newValue: value, added, removed };
19368
- }
19369
- else {
19370
- const added = newArray.filter((item) => !oldArray.some((oldItem) => isEqual$1(item, oldItem)));
19371
- const removed = oldArray.filter((item) => !newArray.some((newItem) => isEqual$1(item, newItem)));
19372
- result[key] = { oldValue, newValue: value, added, removed };
19373
- }
19374
- }
19375
- else {
19376
- result[key] = { oldValue, newValue: value };
19377
- }
19378
- }
19379
- }, {});
19380
- }
19381
- //#endregion
19382
-
19383
20334
  /**
19384
20335
  * Generated bundle index. Do not edit.
19385
20336
  */
19386
20337
 
19387
- export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, 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, EntityBuilder, EntityDataAccessor, actionExists, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, getMasterInterfacePropertySortKey, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, provideEntity, resolveEntityPluginDetailPageOrder, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider };
20338
+ export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, 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, EntityBuilder, EntityDataAccessor, actionExists, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, cloneLayoutArrays, collectNestedCreateHiddenProperties, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, getMasterInterfacePropertySortKey, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mergeForeignKeyFieldIntoCreateActions, provideEntity, resolveEntityPluginDetailPageOrder, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider };
19388
20339
  //# sourceMappingURL=acorex-platform-layout-entity.mjs.map