@acorex/platform 20.9.1 → 20.9.2

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.
@@ -10,11 +10,11 @@ import * as i1$3 from '@acorex/platform/workflow';
10
10
  import { AXPWorkflowEventService, AXPWorkflowService, ofType, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowModule } from '@acorex/platform/workflow';
11
11
  import * as i0 from '@angular/core';
12
12
  import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, ChangeDetectorRef, input, signal, computed, effect, ChangeDetectionStrategy, Component, ElementRef, NgModule, viewChild, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, EnvironmentInjector, makeEnvironmentProviders } from '@angular/core';
13
- import { cloneDeep, merge, get, castArray, set, orderBy, isNil, isEmpty, isEqual, isObjectLike, isArray, has, isString } from 'lodash-es';
13
+ import { cloneDeep, merge, get, castArray, set, orderBy, defaultTo, isNil, isEmpty, isEqual, isObjectLike, isArray, has, isString } from 'lodash-es';
14
14
  import { Subject, filter, takeUntil } from 'rxjs';
15
15
  import { AXPWidgetsCatalog, resolveActionLook, AXPPlatformScope, extractValue, setSmart, getChangedPaths, isSelectionValueEqual, AXPDataGenerator, AXPSystemActionType } from '@acorex/platform/contracts';
16
16
  import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
17
- import { AXPDeviceService, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPBroadcastEventService, applyFilterArray, applySortArray, AXPDistributedEventListenerService, AXHighlightService, AXPDebugService, AXPContextStore, FORM_DIRTY_TRACE_NS, objectKeyValueTransforms, setupPopupFooterKeyboardShortcuts, AXPHookService, AXPComponentSlotModule, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
17
+ import { AXPDeviceService, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPBroadcastEventService, applyFilterArray, applySortArray, AXPDistributedEventListenerService, AXHighlightService, AXPColumnWidthService, AXPDebugService, AXPContextStore, FORM_DIRTY_TRACE_NS, objectKeyValueTransforms, setupPopupFooterKeyboardShortcuts, AXPHookService, AXPComponentSlotModule, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
18
18
  import { AXPSessionService, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
19
19
  import { Router, NavigationStart, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
20
20
  import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY } from '@acorex/platform/runtime-contracts';
@@ -1385,9 +1385,6 @@ class PropertyFilter {
1385
1385
  });
1386
1386
  }
1387
1387
  wizardExtraSteps.sort((a, b) => a.order - b.order);
1388
- const pageWizardWidgetNames = wizardExtraSteps
1389
- .filter((s) => s.kind === 'page')
1390
- .map((s) => buildPageComponentWidgetName(s.page));
1391
1388
  const prebuiltRelatedListConfigs = useFormWizard
1392
1389
  ? await Promise.all(wizardExtraSteps
1393
1390
  .filter((s) => s.kind === 'related-list')
@@ -1485,7 +1482,7 @@ class PropertyFilter {
1485
1482
  .submit(this.kind === 'create' ? '@general:actions.create.title' : '@general:actions.apply.title'));
1486
1483
  }
1487
1484
  if (useFormWizard && this.onActionHandler) {
1488
- d.onAction(this.createWizardOnActionWrapper(this.onActionHandler, pageWizardWidgetNames));
1485
+ d.onAction(this.createWizardOnActionWrapper(this.onActionHandler));
1489
1486
  }
1490
1487
  else if (this.onActionHandler) {
1491
1488
  d.onAction(this.onActionHandler);
@@ -1649,11 +1646,14 @@ class PropertyFilter {
1649
1646
  return (await this.expressionEvaluator.evaluate(action, scope));
1650
1647
  }));
1651
1648
  }
1652
- createWizardOnActionWrapper(userHandler, pageWizardWidgetNames = []) {
1649
+ createWizardOnActionWrapper(userHandler) {
1653
1650
  return async (ref) => {
1654
1651
  const action = ref.action();
1655
1652
  if (action === ENTITY_FORM_ACTION_DONE) {
1656
- await syncAllPageComponentWidgets(ref, pageWizardWidgetNames);
1653
+ const pageContinue = await continueActiveEntityFormPageStep(ref);
1654
+ if (!pageContinue.success) {
1655
+ return { keepDialogOpen: true };
1656
+ }
1657
1657
  if (hasPersistedRootId$1(ref.context())) {
1658
1658
  const out = (await userHandler(ref));
1659
1659
  if (out?.success === false) {
@@ -1664,7 +1664,10 @@ class PropertyFilter {
1664
1664
  return { success: true, data: ref.context(), skipValidate: true };
1665
1665
  }
1666
1666
  if (action === ENTITY_FORM_ACTION_NEXT_STEP) {
1667
- await syncAllPageComponentWidgets(ref, pageWizardWidgetNames);
1667
+ const pageContinue = await continueActiveEntityFormPageStep(ref);
1668
+ if (!pageContinue.success) {
1669
+ return { keepDialogOpen: true };
1670
+ }
1668
1671
  await ref.invokeWidget?.(ENTITY_FORM_STEP_WIZARD_NAME, 'next', { setLoading: ref.setLoading });
1669
1672
  return { keepDialogOpen: true };
1670
1673
  }
@@ -1673,7 +1676,7 @@ class PropertyFilter {
1673
1676
  if (out?.success === false) {
1674
1677
  return out;
1675
1678
  }
1676
- const item = out?.data?.item ?? out?.data ?? {};
1679
+ const item = normalizeWizardPersistedRecordPatch(out?.data?.item ?? out?.data ?? {});
1677
1680
  ref.patchContext?.(merge({}, ref.context(), item));
1678
1681
  await ref.invokeWidget?.(ENTITY_FORM_STEP_WIZARD_NAME, 'next', { setLoading: ref.setLoading });
1679
1682
  return merge({}, out, { keepDialogOpen: true });
@@ -2042,10 +2045,27 @@ function hasPersistedRootId$1(context) {
2042
2045
  const raw = context?.id ?? context?._id;
2043
2046
  return (typeof raw === 'string' && raw.length > 0) || (typeof raw === 'number' && !Number.isNaN(raw));
2044
2047
  }
2045
- async function syncAllPageComponentWidgets(ref, widgetNames) {
2046
- for (const name of widgetNames) {
2047
- await ref.invokeWidget?.(name, 'syncContext', {});
2048
+ function normalizeWizardPersistedRecordPatch(data) {
2049
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
2050
+ return {};
2051
+ }
2052
+ const record = { ...data };
2053
+ const rawId = record['id'] ?? record['_id'];
2054
+ if (rawId != null && record['id'] == null) {
2055
+ record['id'] = rawId;
2056
+ }
2057
+ return record;
2058
+ }
2059
+ async function continueActiveEntityFormPageStep(ref) {
2060
+ const status = (await ref.invokeWidget?.(ENTITY_FORM_STEP_WIZARD_NAME, 'getStatus', {}));
2061
+ const stepId = status?.currentStep?.id;
2062
+ if (!stepId?.startsWith('page-')) {
2063
+ return { success: true };
2048
2064
  }
2065
+ const result = (await ref.invokeWidget?.(`pageComponent_${stepId}`, 'continueWizardStep', {
2066
+ setLoading: ref.setLoading,
2067
+ }));
2068
+ return { success: result?.success !== false };
2049
2069
  }
2050
2070
  function buildEntityFormRelatedStepId(re) {
2051
2071
  const safe = re.entity.replace(/[^a-zA-Z0-9._-]/g, '_');
@@ -4374,6 +4394,7 @@ class AXPEntityListViewColumnViewModel {
4374
4394
  this.title = this.column.title ?? this.property.title;
4375
4395
  this.visible = this.column?.options?.visible ?? true;
4376
4396
  this.sortEnabled = this.property.options?.sort?.enabled;
4397
+ this.width = this.column?.options?.width;
4377
4398
  this.node = computed(() => {
4378
4399
  const widget = this.property.schema.interface;
4379
4400
  return {
@@ -5787,10 +5808,11 @@ class AXPEntityMasterListViewModel {
5787
5808
  if (columns && columnVisibilityMap && columnWidthsMap) {
5788
5809
  this.columns.update((prev) => prev
5789
5810
  .map((c) => {
5811
+ const columnKey = c.column?.options?.dataPath ?? c.name;
5790
5812
  return {
5791
5813
  ...c,
5792
- width: columnWidthsMap.get(c.column?.options?.dataPath ?? c.name),
5793
- visible: columnVisibilityMap.get(c.column?.options?.dataPath ?? c.name) ?? c.visible,
5814
+ width: defaultTo(columnWidthsMap.get(columnKey), c.column?.options?.width),
5815
+ visible: columnVisibilityMap.get(columnKey) ?? c.visible,
5794
5816
  };
5795
5817
  }) // Update visibility
5796
5818
  .sort((a, b) => columns.findIndex((col) => col.name === (a.column?.options?.dataPath ?? a.name)) -
@@ -12033,6 +12055,26 @@ function mapEntityColumnToWidgetColumn(entity, column) {
12033
12055
  widget,
12034
12056
  };
12035
12057
  }
12058
+ /**
12059
+ * Applies entity column width (explicit or resolved via {@link AXPColumnWidthService}) to data-list columns.
12060
+ */
12061
+ function applyDataListColumnWidths(columns, columnWidthService) {
12062
+ return columns.map((column) => {
12063
+ const existingWidth = column.width ?? column.widget?.options?.width;
12064
+ if (existingWidth) {
12065
+ return existingWidth === column.width ? column : { ...column, width: String(existingWidth) };
12066
+ }
12067
+ const columnWithWidth = {
12068
+ name: column.name,
12069
+ widget: column.widget ? { type: column.widget.type } : undefined,
12070
+ };
12071
+ const resolvedWidth = columnWidthService.resolveWidth(columnWithWidth);
12072
+ if (!resolvedWidth) {
12073
+ return column;
12074
+ }
12075
+ return { ...column, width: resolvedWidth };
12076
+ });
12077
+ }
12036
12078
  //#endregion
12037
12079
  //#region ---- Include/Exclude Utilities ----
12038
12080
  /**
@@ -12063,6 +12105,7 @@ class AXPEntityListTableService {
12063
12105
  this.commandService = inject(AXPCommandService);
12064
12106
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
12065
12107
  this.settings = inject(AXPSettingsService);
12108
+ this.columnWidthService = inject(AXPColumnWidthService);
12066
12109
  this.evaluateExpressions = async (options, data) => {
12067
12110
  if (!options) {
12068
12111
  return {};
@@ -12164,7 +12207,7 @@ class AXPEntityListTableService {
12164
12207
  else {
12165
12208
  baseColumns = getVisibleProperties(entity).map((prop) => mapPropertyToWidgetColumn(prop));
12166
12209
  }
12167
- return applyIncludeExclude(baseColumns, includeColumns, excludeColumns);
12210
+ return applyDataListColumnWidths(applyIncludeExclude(baseColumns, includeColumns, excludeColumns), this.columnWidthService);
12168
12211
  }
12169
12212
  /**
12170
12213
  * Map EntityTableColumn to ListWidgetColumn
@@ -12483,6 +12526,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
12483
12526
  this.commandService = inject(AXPCommandService);
12484
12527
  this.eventService = inject(AXPBroadcastEventService);
12485
12528
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
12529
+ this.columnWidthService = inject(AXPColumnWidthService);
12486
12530
  this.route = inject(ActivatedRoute);
12487
12531
  this.isMounted = signal(false, ...(ngDevMode ? [{ debugName: "isMounted" }] : /* istanbul ignore next */ []));
12488
12532
  this.entity = signal(null, ...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
@@ -13039,7 +13083,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
13039
13083
  })
13040
13084
  .filter((c) => c != null);
13041
13085
  // Apply updated columns to the list widget and refresh
13042
- listInstance.setOptions({ columns: updatedColumns });
13086
+ listInstance.setOptions({
13087
+ columns: applyDataListColumnWidths(updatedColumns, this.columnWidthService),
13088
+ });
13043
13089
  }
13044
13090
  async ngOnInit() {
13045
13091
  super.ngOnInit();
@@ -13406,6 +13452,8 @@ class AXPPageComponentWidgetViewComponent extends AXPBaseWidgetComponent {
13406
13452
  super(...arguments);
13407
13453
  //#region ---- Services & Dependencies ----
13408
13454
  this.deviceService = inject(AXPDeviceService);
13455
+ this.toastService = inject(AXToastService);
13456
+ this.translationService = inject(AXTranslationService);
13409
13457
  this.pageRenderer = viewChild(AXPPageComponentRendererDirective, ...(ngDevMode ? [{ debugName: "pageRenderer" }] : /* istanbul ignore next */ []));
13410
13458
  //#endregion
13411
13459
  //#region ---- Computed Options ----
@@ -13433,7 +13481,6 @@ class AXPPageComponentWidgetViewComponent extends AXPBaseWidgetComponent {
13433
13481
  }
13434
13482
  instance.primaryMenuItems();
13435
13483
  instance.secondaryMenuItems();
13436
- console.log({ primaryMenuItems: instance.primaryMenuItems(), secondaryMenuItems: instance.secondaryMenuItems() });
13437
13484
  untracked(() => this.syncActionsFromPageInstance(instance));
13438
13485
  }, ...(ngDevMode ? [{ debugName: "#syncPageActionsEffect" }] : /* istanbul ignore next */ []));
13439
13486
  }
@@ -13457,6 +13504,15 @@ class AXPPageComponentWidgetViewComponent extends AXPBaseWidgetComponent {
13457
13504
  syncContext: () => {
13458
13505
  self.syncPageComponentIntoFormContext();
13459
13506
  },
13507
+ continueWizardStep: async (callback) => {
13508
+ callback?.setLoading?.(true);
13509
+ try {
13510
+ return await self.runWizardStepContinue();
13511
+ }
13512
+ finally {
13513
+ callback?.setLoading?.(false);
13514
+ }
13515
+ },
13460
13516
  refreshPageActions: () => {
13461
13517
  self.refreshPageActions();
13462
13518
  },
@@ -13490,6 +13546,66 @@ class AXPPageComponentWidgetViewComponent extends AXPBaseWidgetComponent {
13490
13546
  this.pageHostRevision.update((value) => value + 1);
13491
13547
  }
13492
13548
  //#endregion
13549
+ //#region ---- Wizard Step Continue ----
13550
+ /**
13551
+ * Persists the active page step when dirty (footer save action), then merges wizard context.
13552
+ * Used by entity create/update step wizard footer Next/Done.
13553
+ */
13554
+ async runWizardStepContinue() {
13555
+ const instance = this.resolvePageHost();
13556
+ if (!instance) {
13557
+ return { success: true };
13558
+ }
13559
+ const dirty = instance.hasUnsavedChanges?.() ?? false;
13560
+ if (dirty) {
13561
+ const command = this.resolveWizardContinueCommand(instance);
13562
+ if (command.name) {
13563
+ const result = await instance.execute(command);
13564
+ if (isExecuteCommandFailure(result)) {
13565
+ await this.showExecuteFailureToast(result);
13566
+ return { success: false };
13567
+ }
13568
+ }
13569
+ }
13570
+ this.syncPageComponentIntoFormContext();
13571
+ this.pageHostRevision.update((value) => value + 1);
13572
+ return { success: true };
13573
+ }
13574
+ resolveWizardContinueCommand(instance) {
13575
+ const explicit = instance.getWizardStepContinueCommand?.();
13576
+ if (explicit?.name) {
13577
+ return explicit;
13578
+ }
13579
+ const actions = [...instance.primaryMenuItems(), ...instance.secondaryMenuItems()];
13580
+ const continueAction = actions.find((action) => action.visible !== false &&
13581
+ isFooterZoneAction(action) &&
13582
+ getActionPriority(action) === 'primary' &&
13583
+ action.command);
13584
+ return continueAction ? normalizeExecuteCommand(continueAction.command) : { name: '', options: {} };
13585
+ }
13586
+ async showExecuteFailureToast(result) {
13587
+ const rawText = result.message?.text;
13588
+ let content;
13589
+ if (typeof rawText === 'string') {
13590
+ content = rawText.startsWith('@') ? await this.translationService.translateAsync(rawText) : rawText;
13591
+ }
13592
+ else if (rawText) {
13593
+ content = this.translationService.resolve(rawText);
13594
+ }
13595
+ else {
13596
+ content = await this.translationService.translateAsync('@general:messages.generic.error.description');
13597
+ }
13598
+ this.toastService.show({
13599
+ color: 'danger',
13600
+ title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
13601
+ content,
13602
+ location: 'bottom-center',
13603
+ closeButton: true,
13604
+ timeOut: 5000,
13605
+ timeOutProgress: true,
13606
+ });
13607
+ }
13608
+ //#endregion
13493
13609
  //#region ---- Context Sync ----
13494
13610
  /** Merges the mounted page component's wizard payload into the dialog form context. */
13495
13611
  syncPageComponentIntoFormContext() {
@@ -13730,6 +13846,15 @@ function splitToolbarSecondaryActions(primary, secondary) {
13730
13846
  function isHeaderZoneAction(action) {
13731
13847
  return action.zone === 'header';
13732
13848
  }
13849
+ function isFooterZoneAction(action) {
13850
+ return action.zone === 'footer';
13851
+ }
13852
+ function isExecuteCommandFailure(result) {
13853
+ return (!!result &&
13854
+ typeof result === 'object' &&
13855
+ 'success' in result &&
13856
+ result.success === false);
13857
+ }
13733
13858
  function getActionPriority(action) {
13734
13859
  const priority = action.priority;
13735
13860
  return priority === 'secondary' ? 'secondary' : 'primary';