@acorex/platform 20.9.38 → 20.9.39

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.
@@ -1,4 +1,4 @@
1
- import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, AXPEntityCommandScope, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, resolveEntityListFilterContextField, resolveEntityListFilterQueryField, AXPEntityDataSourceKeys, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, isEntityActionPermitted, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION, resolveLookupWidgetCreateOptions, buildPageListWidgetName, AXPEntityQueryType, buildEntitySearchTitleContext } from '@acorex/platform/layout/entity-contracts';
1
+ import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, AXPEntityCommandScope, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, resolveEntityListFilterContextField, resolveEntityListFilterQueryField, AXPEntityDataSourceKeys, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, isEntityActionPermitted, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION, buildPageListWidgetName, resolveLookupWidgetCreateOptions, AXPEntityQueryType, buildEntitySearchTitleContext } from '@acorex/platform/layout/entity-contracts';
2
2
  export * from '@acorex/platform/layout/entity-contracts';
3
3
  export { collectNestedCreateHiddenProperties, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, mergeForeignKeyFieldIntoCreateActions } from '@acorex/platform/layout/entity-contracts';
4
4
  import { AXToastService } from '@acorex/components/toast';
@@ -4552,12 +4552,14 @@ class AXPEntityListViewColumnViewModel {
4552
4552
  this.width = this.column?.options?.width;
4553
4553
  this.node = computed(() => {
4554
4554
  const widget = this.property.schema.interface;
4555
+ // Column `visible` is resolved by the list viewmodel; do not forward expressions into widget options.
4556
+ const { visible: _columnVisible, ...columnOptions } = this.column?.options ?? {};
4555
4557
  return {
4556
4558
  path: this.column.options?.dataPath ?? this.name,
4557
4559
  type: widget.type,
4558
4560
  options: {
4559
4561
  ...widget?.options,
4560
- ...this.column?.options,
4562
+ ...columnOptions,
4561
4563
  columnName: this.name,
4562
4564
  // Include children from widget interface so column components can access them
4563
4565
  ...(widget.children ? { children: widget.children } : {}),
@@ -5587,7 +5589,7 @@ class AXPEntityMasterListViewModel {
5587
5589
  if (shouldApplyViewState) {
5588
5590
  this.view.set(nextView);
5589
5591
  this.applyViewSorts();
5590
- this.applyViewColumns();
5592
+ await this.applyViewColumns();
5591
5593
  this.applyCardFields();
5592
5594
  this.applyViewFilters();
5593
5595
  this.resolvedListPaging.set(null);
@@ -5971,7 +5973,12 @@ class AXPEntityMasterListViewModel {
5971
5973
  for (const column of next) {
5972
5974
  const columnKey = column.column?.options?.dataPath ?? column.name;
5973
5975
  column.width = defaultTo(columnWidthsMap.get(columnKey), column.column?.options?.width);
5974
- column.visible = columnVisibilityMap.get(columnKey) ?? column.visible;
5976
+ // Definition visibility (view.columns whitelist + options.visible) always wins for "hide".
5977
+ // Persisted settings may only further hide an already-allowed column.
5978
+ const definitionVisible = column.visible === true;
5979
+ const persistedVisible = columnVisibilityMap.get(columnKey);
5980
+ column.visible =
5981
+ definitionVisible && (persistedVisible === undefined ? true : persistedVisible !== false);
5975
5982
  }
5976
5983
  return next;
5977
5984
  });
@@ -6107,7 +6114,7 @@ class AXPEntityMasterListViewModel {
6107
6114
  const existingColumns = prev?.list?.views?.[this.view().name]?.columns;
6108
6115
  const baseColumns = Array.isArray(existingColumns) && existingColumns.length
6109
6116
  ? existingColumns
6110
- : this.allAvailableColumns().map((c) => ({
6117
+ : this.columns().map((c) => ({
6111
6118
  name: c.column?.options?.dataPath ?? c.name,
6112
6119
  visible: c.visible,
6113
6120
  width: c.width,
@@ -6116,11 +6123,6 @@ class AXPEntityMasterListViewModel {
6116
6123
  ...c,
6117
6124
  width: c.name === field ? columnSizeData.width : c.width,
6118
6125
  }));
6119
- const x = this.allAvailableColumns().map((c) => ({
6120
- name: c.column?.options?.dataPath ?? c.name,
6121
- visible: c.visible,
6122
- width: c.width,
6123
- }));
6124
6126
  set(newSettings, `list.views.${this.view().name}.columns`, updatedColumns);
6125
6127
  return newSettings;
6126
6128
  });
@@ -6638,22 +6640,59 @@ class AXPEntityMasterListViewModel {
6638
6640
  const relatedDef = this.getRegisteredEntity(related.entity);
6639
6641
  return relatedDef?.properties?.find((property) => property.name === propertyName) ?? null;
6640
6642
  }
6641
- resetColumns() {
6642
- this.applyViewColumns();
6643
+ async resetColumns() {
6644
+ await this.applyViewColumns();
6643
6645
  }
6644
6646
  async onColumnsChanged(columns) {
6645
6647
  this.saveSettings('columnOrders', columns);
6646
6648
  }
6647
- applyViewColumns() {
6648
- const cols = this.view().columns;
6649
- const cloned = this.allAvailableColumns().map((c) => {
6649
+ /**
6650
+ * Builds list columns for the active view:
6651
+ * - `view.columns` is a whitelist (empty = all columns)
6652
+ * - string `options.visible` is evaluated against the master-list scope
6653
+ */
6654
+ async applyViewColumns() {
6655
+ const viewColumnNames = this.view().columns ?? [];
6656
+ const scope = this.createLogicExpressionScope(null);
6657
+ const cloned = await Promise.all(this.allAvailableColumns().map(async (c) => {
6650
6658
  const column = this.entityDef.columns?.find((cc) => cc.name == c.name);
6651
6659
  const col = new AXPEntityListViewColumnViewModel(c.property, column);
6652
- col.visible = !cols.some((c) => c == col.name) && col.visible != false;
6660
+ const expressionVisible = await this.resolveColumnVisibleExpression(col.visible, scope);
6661
+ const includedInView = viewColumnNames.length === 0 || viewColumnNames.some((name) => name === col.name);
6662
+ col.visible = includedInView && expressionVisible;
6653
6663
  return col;
6654
- });
6664
+ }));
6665
+ if (viewColumnNames.length > 0) {
6666
+ cloned.sort((a, b) => {
6667
+ const aIndex = viewColumnNames.indexOf(a.name);
6668
+ const bIndex = viewColumnNames.indexOf(b.name);
6669
+ const aOrder = aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex;
6670
+ const bOrder = bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex;
6671
+ return aOrder - bOrder;
6672
+ });
6673
+ }
6655
6674
  this.columns.set(cloned);
6656
6675
  }
6676
+ /**
6677
+ * Resolves column `options.visible` (boolean or expression) to a boolean for the current list scope.
6678
+ */
6679
+ async resolveColumnVisibleExpression(visible, scope) {
6680
+ if (visible === false) {
6681
+ return false;
6682
+ }
6683
+ if (visible === true || visible == null || visible === '') {
6684
+ return true;
6685
+ }
6686
+ if (typeof visible === 'string') {
6687
+ if (!visible.includes('{{')) {
6688
+ const normalized = visible.trim().toLowerCase();
6689
+ return normalized !== 'false' && normalized !== '0';
6690
+ }
6691
+ const result = await this.expressionEvaluator.evaluate(visible, scope);
6692
+ return result !== false;
6693
+ }
6694
+ return Boolean(visible);
6695
+ }
6657
6696
  onColumnsOrderChanged(event) {
6658
6697
  const columns = [...this.columns()];
6659
6698
  moveItemInArray(columns, event.previousIndex - 1, event.currentIndex - 1);
@@ -13131,6 +13170,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13131
13170
  /**
13132
13171
  * Route `filters` apply only when the active details `page` matches this list's entity
13133
13172
  * (e.g. WorkOrder filters must not affect FailureRegister on the same asset layout).
13173
+ * page-list sidebar pages use `page-list-{module.entity}` as their page id.
13134
13174
  */
13135
13175
  shouldApplyRouteFilters() {
13136
13176
  const pageId = this.route.snapshot.queryParamMap.get('page');
@@ -13142,7 +13182,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13142
13182
  return false;
13143
13183
  }
13144
13184
  const [, entityName] = entitySource.split('.');
13145
- return !!entityName && pageId === entityName;
13185
+ return ((!!entityName && pageId === entityName) || pageId === buildPageListWidgetName(entitySource));
13146
13186
  }
13147
13187
  /**
13148
13188
  * Merges route filters into related-entity toolbar filters (route wins per field).
@@ -23406,8 +23446,10 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
23406
23446
  hidden: true,
23407
23447
  };
23408
23448
  }) ?? []);
23449
+ // Unique from host overview page id (`entity.name`) so self-referential related lists navigate correctly.
23450
+ const pageId = buildPageListWidgetName(relatedEntity.entity);
23409
23451
  return {
23410
- id: entityDef?.name ?? '',
23452
+ id: pageId,
23411
23453
  title: context.rootTitle ?? '',
23412
23454
  label: relatedEntity.title,
23413
23455
  icon: relatedEntity.icon || entityDef.icon,
@@ -24638,11 +24680,14 @@ class AXPLayoutAdapterFactory {
24638
24680
  composePagesWithPositions(mainPage, relatedPages, entity) {
24639
24681
  // Only consider related entities with layout types page-detail or page-list
24640
24682
  const pageEntities = (entity?.relatedEntities || []).filter((re) => !re.hidden && (re.layout?.type === 'page-detail' || re.layout?.type === 'page-list'));
24641
- // Build a map from entity name to its layout config (order/position)
24683
+ // Build a map from page id to its layout config (order/position).
24684
+ // page-list ids use `page-list-{module.entity}` so they never collide with the host overview page.
24642
24685
  const layoutConfigByEntity = {};
24643
24686
  for (const re of pageEntities) {
24644
24687
  const [, entityName] = (re.entity || '').split('.');
24645
- const key = entityName || re.entity;
24688
+ const key = re.layout?.type === 'page-list'
24689
+ ? buildPageListWidgetName(re.entity)
24690
+ : entityName || re.entity;
24646
24691
  layoutConfigByEntity[key] = { order: re.layout?.order, position: re.layout?.position };
24647
24692
  }
24648
24693
  // Nested pages from merge-detail (e.g. PartyContact from Party) have no layout config.