@acorex/platform 21.0.0-next.40 → 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 (22) hide show
  1. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  2. package/fesm2022/acorex-platform-layout-builder.mjs +115 -29
  3. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  4. package/fesm2022/acorex-platform-layout-designer.mjs +59 -4
  5. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-layout-entity.mjs +995 -545
  7. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  9. package/fesm2022/{acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs → acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs} +9 -4
  10. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-layout-widgets.mjs +418 -106
  12. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-workflow.mjs +2 -1
  14. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/types/acorex-platform-common.d.ts +14 -10
  17. package/types/acorex-platform-layout-builder.d.ts +20 -2
  18. package/types/acorex-platform-layout-designer.d.ts +35 -3
  19. package/types/acorex-platform-layout-entity.d.ts +46 -5
  20. package/types/acorex-platform-layout-widget-core.d.ts +2 -5
  21. package/types/acorex-platform-layout-widgets.d.ts +108 -51
  22. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +0 -1
@@ -6,7 +6,7 @@ import { provideCommandSetups } from '@acorex/platform/runtime';
6
6
  import { AXPopupService } from '@acorex/components/popup';
7
7
  import * as i1 from '@acorex/platform/layout/widget-core';
8
8
  import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
9
- import { cloneDeep, isNil, set, isEqual } from 'lodash-es';
9
+ import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
10
10
  import * as i2 from '@acorex/components/form';
11
11
  import { AXFormComponent, AXFormModule } from '@acorex/components/form';
12
12
  import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
@@ -531,11 +531,21 @@ class LayoutBuilder {
531
531
  return this;
532
532
  }
533
533
  build() {
534
- return {
535
- type: this.root.type,
536
- children: this.root.children,
537
- mode: this.root.mode,
534
+ const r = this.root;
535
+ const node = {
536
+ type: r.type,
537
+ ...(r.mode !== undefined ? { mode: r.mode } : {}),
538
+ ...(r.children !== undefined ? { children: r.children } : {}),
539
+ ...(r.options !== undefined ? { options: r.options } : {}),
540
+ ...(r.name !== undefined ? { name: r.name } : {}),
541
+ ...(r.path !== undefined ? { path: r.path } : {}),
542
+ ...(r.visible !== undefined ? { visible: r.visible } : {}),
543
+ ...(r.defaultValue !== undefined ? { defaultValue: r.defaultValue } : {}),
544
+ ...(r.triggers !== undefined ? { triggers: r.triggers } : {}),
545
+ ...(r.meta !== undefined ? { meta: r.meta } : {}),
546
+ ...(r.valueTransforms !== undefined ? { valueTransforms: r.valueTransforms } : {}),
538
547
  };
548
+ return node;
539
549
  }
540
550
  /**
541
551
  * Converts the built widget node to JSON string
@@ -1009,6 +1019,24 @@ class FlexContainerBuilder extends WidgetContainerMixin {
1009
1019
  * Extracts flat grid-item options from AXPGridLayoutOptions for grid-item-layout widget.
1010
1020
  * Uses first available breakpoint (lg, xl, md, sm).
1011
1021
  */
1022
+ /**
1023
+ * Deep-merges grid breakpoint buckets so sequential fluent calls (e.g. setColumns then setGap)
1024
+ * do not wipe sibling keys under options.grid.default.
1025
+ */
1026
+ function mergeAXPGridContainerOptions(prev, patch) {
1027
+ const next = { ...(prev ?? {}), ...patch };
1028
+ if (prev?.grid?.default || patch.grid?.default) {
1029
+ next.grid = {
1030
+ ...(prev?.grid ?? {}),
1031
+ ...(patch.grid ?? {}),
1032
+ default: {
1033
+ ...(prev?.grid?.default ?? {}),
1034
+ ...(patch.grid?.default ?? {}),
1035
+ },
1036
+ };
1037
+ }
1038
+ return next;
1039
+ }
1012
1040
  function toGridItemOptions(layoutOptions) {
1013
1041
  if (!layoutOptions?.positions)
1014
1042
  return { colSpan: 12 };
@@ -1038,7 +1066,7 @@ class GridContainerBuilder extends WidgetContainerMixin {
1038
1066
  super('grid-layout');
1039
1067
  }
1040
1068
  setOptions(options) {
1041
- this.containerState.options = { ...this.containerState.options, ...options };
1069
+ this.containerState.options = mergeAXPGridContainerOptions(this.containerState.options, options);
1042
1070
  return this;
1043
1071
  }
1044
1072
  item(layoutOptions, delegate) {
@@ -2345,7 +2373,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2345
2373
  }
2346
2374
  async executeAction(action) {
2347
2375
  const cmd = this.resolveActionCommandName(action.command);
2348
- if (cmd !== 'cancel') {
2376
+ if (this.shouldValidateBeforeAction(cmd)) {
2349
2377
  const isValid = await this.layoutRenderer()?.validate();
2350
2378
  if (!isValid?.result) {
2351
2379
  return;
@@ -2354,7 +2382,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2354
2382
  if (cmd?.startsWith('widget:')) {
2355
2383
  const parsed = this.parseWidgetCommand(cmd);
2356
2384
  if (parsed.widgetName && parsed.action) {
2357
- await this.executeWidgetApi(parsed.widgetName, parsed.action);
2385
+ await this.invokeWidget(parsed.widgetName, parsed.action, {});
2358
2386
  await this.aggregateAndEvaluateActions();
2359
2387
  return;
2360
2388
  }
@@ -2362,17 +2390,15 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2362
2390
  const context = this.context();
2363
2391
  const onAction = this.config?.onAction;
2364
2392
  if (onAction) {
2365
- const dialogRef = {
2366
- close: (res) => this.close(res),
2367
- context: () => this.context(),
2368
- action: () => action.command ?? undefined,
2369
- setLoading: (loading) => this.isDialogLoading.set(loading),
2370
- };
2393
+ const dialogRef = this.createDialogRef(cmd);
2371
2394
  try {
2372
2395
  this.isDialogLoading.set(true);
2373
2396
  const result = await Promise.resolve(onAction(dialogRef));
2397
+ if (result && typeof result === 'object' && result.keepDialogOpen) {
2398
+ return;
2399
+ }
2374
2400
  this.callBack(result);
2375
- this.close(result);
2401
+ await this.closeWithOptionalSkipValidate(result);
2376
2402
  }
2377
2403
  catch {
2378
2404
  // Handler threw: stay open for retry, actions remain clickable
@@ -2390,20 +2416,48 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2390
2416
  this.data.action = result.action;
2391
2417
  }
2392
2418
  this.callBack({
2393
- close: (res) => {
2394
- this.close(res);
2395
- },
2396
- context: () => this.context(),
2419
+ ...this.createDialogRef(cmd),
2397
2420
  action: () => result.action,
2398
- setLoading: (loading) => {
2399
- this.isDialogLoading.set(loading);
2400
- },
2401
2421
  });
2402
2422
  // Without `onAction`, only the configured cancel action dismisses the dialog (not submit/custom).
2403
2423
  if (cmd === 'cancel') {
2404
2424
  await this.close(result);
2405
2425
  }
2406
2426
  }
2427
+ /** Whether the layout form should be validated before running this footer command. */
2428
+ shouldValidateBeforeAction(cmd) {
2429
+ if (!cmd || cmd === 'cancel' || cmd === 'entity-form-done') {
2430
+ return false;
2431
+ }
2432
+ if (cmd.startsWith('widget:')) {
2433
+ return false;
2434
+ }
2435
+ return true;
2436
+ }
2437
+ createDialogRef(actionCmd) {
2438
+ return {
2439
+ close: (res) => {
2440
+ void this.closeWithOptionalSkipValidate(res);
2441
+ },
2442
+ context: () => this.context(),
2443
+ action: () => actionCmd,
2444
+ setLoading: (loading) => this.isDialogLoading.set(loading),
2445
+ patchContext: (partial) => {
2446
+ const merged = merge({}, this.context(), partial);
2447
+ this.context.set(merged);
2448
+ this.layoutRenderer()?.updateContext(merged);
2449
+ },
2450
+ invokeWidget: (widgetName, method, opts) => this.invokeWidget(widgetName, method, opts ?? {}),
2451
+ };
2452
+ }
2453
+ async closeWithOptionalSkipValidate(result) {
2454
+ if (result && typeof result === 'object' && result.skipValidate) {
2455
+ this.result.emit(result);
2456
+ await super.close(result);
2457
+ return;
2458
+ }
2459
+ await this.close(result);
2460
+ }
2407
2461
  /** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
2408
2462
  resolveActionCommandName(command) {
2409
2463
  if (typeof command === 'string') {
@@ -2424,7 +2478,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2424
2478
  return {};
2425
2479
  return { widgetName: rest.slice(0, dot), action: rest.slice(dot + 1) };
2426
2480
  }
2427
- async executeWidgetApi(widgetName, apiMethod) {
2481
+ async invokeWidget(widgetName, apiMethod, opts) {
2428
2482
  if (!this.widgetCoreService)
2429
2483
  return;
2430
2484
  try {
@@ -2434,16 +2488,20 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2434
2488
  if (typeof fn === 'function') {
2435
2489
  await Promise.resolve(fn({
2436
2490
  close: (result) => {
2437
- this.close(result);
2491
+ void this.closeWithOptionalSkipValidate(result);
2438
2492
  },
2439
2493
  context: () => this.context(),
2440
2494
  setLoading: (loading) => {
2441
- this.isDialogLoading.set(loading);
2495
+ (opts.setLoading ?? ((v) => this.isDialogLoading.set(v)))(loading);
2442
2496
  },
2443
2497
  }));
2498
+ // Footer predicates (e.g. wizard step) must refresh when the widget advances outside executeAction (e.g. dialogRef.invokeWidget after entity-form continue).
2499
+ await this.aggregateAndEvaluateActions();
2444
2500
  }
2445
2501
  }
2446
- catch { }
2502
+ catch {
2503
+ //
2504
+ }
2447
2505
  }
2448
2506
  async close(result) {
2449
2507
  if (result) {
@@ -2493,6 +2551,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2493
2551
  zone: 'footer',
2494
2552
  placement,
2495
2553
  scope: a.scope,
2554
+ predicateApiWidgetName: a.predicateApiWidgetName,
2496
2555
  });
2497
2556
  const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
2498
2557
  const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
@@ -2502,16 +2561,18 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2502
2561
  const out = [];
2503
2562
  for (const a of actions) {
2504
2563
  const parsed = typeof a.command === 'string' ? this.parseWidgetCommand(a.command) : {};
2505
- const api = parsed.widgetName ? await this.resolveApi(parsed.widgetName) : undefined;
2564
+ const widgetNameForApi = parsed.widgetName ?? a.predicateApiWidgetName;
2565
+ const api = widgetNameForApi ? await this.resolveApi(widgetNameForApi) : undefined;
2506
2566
  const scope = {
2507
2567
  api,
2508
- widget: { name: parsed.widgetName },
2568
+ widget: { name: widgetNameForApi },
2509
2569
  dialog: { context: this.context() },
2510
2570
  context: this.context(),
2511
2571
  };
2512
2572
  const disabled = await this.evalBool(a.disabled, scope);
2513
2573
  const hidden = await this.evalBool(a.hidden, scope);
2514
- out.push({ ...a, disabled, hidden });
2574
+ const resolvedTitle = (await this.evalActionTitle(a.title, scope)) ?? a.title;
2575
+ out.push({ ...a, disabled, hidden, title: resolvedTitle });
2515
2576
  }
2516
2577
  return out;
2517
2578
  }
@@ -2529,6 +2590,25 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2529
2590
  }
2530
2591
  return value;
2531
2592
  }
2593
+ /** Resolves footer action title when it contains {{ ... }} (e.g. wizard labels from dialog context). */
2594
+ async evalActionTitle(value, scope) {
2595
+ if (value == null || typeof value !== 'string' || !value.includes('{{')) {
2596
+ return value;
2597
+ }
2598
+ try {
2599
+ const result = await this.expressionEvaluator.evaluate(value, scope);
2600
+ if (typeof result === 'string' && result.length > 0) {
2601
+ return result;
2602
+ }
2603
+ if (result != null && result !== false) {
2604
+ return String(result);
2605
+ }
2606
+ }
2607
+ catch {
2608
+ //
2609
+ }
2610
+ return value;
2611
+ }
2532
2612
  async resolveApi(widgetName) {
2533
2613
  try {
2534
2614
  await this.widgetCoreService?.waitForWidget(widgetName, 2000);
@@ -2543,6 +2623,9 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2543
2623
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPDialogRendererComponent, isStandalone: true, selector: "axp-dialog-renderer", outputs: { result: "result" }, providers: [AXPContextStore], viewQueries: [{ propertyName: "layoutRenderer", first: true, predicate: AXPLayoutRendererComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
2544
2624
  <axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
2545
2625
  <div class="ax-p-4">
2626
+ @if (config.message) {
2627
+ <p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
2628
+ }
2546
2629
  <axp-layout-renderer
2547
2630
  [layout]="config.definition"
2548
2631
  [context]="context()"
@@ -2616,6 +2699,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2616
2699
  template: `
2617
2700
  <axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
2618
2701
  <div class="ax-p-4">
2702
+ @if (config.message) {
2703
+ <p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
2704
+ }
2619
2705
  <axp-layout-renderer
2620
2706
  [layout]="config.definition"
2621
2707
  [context]="context()"