@acorex/platform 21.0.0-next.71 → 21.0.0-next.73

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 (60) hide show
  1. package/contracts/README.md +3 -0
  2. package/fesm2022/acorex-platform-auth.mjs +82 -60
  3. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  4. package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
  5. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  6. package/fesm2022/acorex-platform-common.mjs +3796 -1677
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-contracts.mjs +10 -0
  9. package/fesm2022/acorex-platform-contracts.mjs.map +1 -0
  10. package/fesm2022/acorex-platform-core.mjs +1126 -114
  11. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-builder.mjs +56 -170
  13. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-components.mjs +254 -53
  15. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
  17. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  18. package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
  19. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
  20. package/fesm2022/acorex-platform-layout-entity.mjs +403 -460
  21. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-views.mjs +675 -301
  23. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs +126 -75
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  26. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs} +2 -2
  27. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map} +1 -1
  28. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs} +2 -2
  29. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map} +1 -1
  30. package/fesm2022/acorex-platform-layout-widgets.mjs +450 -750
  31. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  32. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  33. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  34. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  35. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  36. package/fesm2022/acorex-platform-themes-default.mjs +79 -35
  37. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  38. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +1 -1
  39. package/fesm2022/acorex-platform-themes-shared.mjs +3 -0
  40. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  41. package/fesm2022/acorex-platform-workflow.mjs +57 -54
  42. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  43. package/package.json +5 -1
  44. package/types/acorex-platform-auth.d.ts +31 -22
  45. package/types/acorex-platform-common.d.ts +892 -259
  46. package/types/acorex-platform-contracts.d.ts +39 -0
  47. package/types/acorex-platform-core.d.ts +291 -47
  48. package/types/acorex-platform-layout-builder.d.ts +10 -22
  49. package/types/acorex-platform-layout-components.d.ts +86 -14
  50. package/types/acorex-platform-layout-entity.d.ts +218 -212
  51. package/types/acorex-platform-layout-views.d.ts +125 -67
  52. package/types/acorex-platform-layout-widget-core.d.ts +56 -62
  53. package/types/acorex-platform-layout-widgets.d.ts +78 -65
  54. package/types/acorex-platform-themes-default.d.ts +14 -4
  55. package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
  56. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
  57. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
  58. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
  59. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
  60. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
@@ -5,7 +5,7 @@ import { Injectable, inject, input, model, signal, computed, effect, output, vie
5
5
  import { provideCommandSetups, AXPCommandService } from '@acorex/platform/runtime';
6
6
  import { AXPopupService } from '@acorex/components/popup';
7
7
  import * as i4 from '@acorex/platform/core';
8
- import { AXPHookService, AXPExpressionEvaluatorService, captureFormContextBaseline, isFormContextDirty, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
8
+ import { normalizeKeyboardShortcuts, AXPHookService, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
9
9
  import * as i1 from '@acorex/platform/layout/widget-core';
10
10
  import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
11
11
  import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
@@ -15,7 +15,6 @@ import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
15
15
  import { AXOverlayService } from '@acorex/cdk/overlay';
16
16
  import * as i1$1 from '@acorex/components/button';
17
17
  import { AXButtonModule } from '@acorex/components/button';
18
- import { AXDialogService } from '@acorex/components/dialog';
19
18
  import * as i2$1 from '@acorex/components/decorators';
20
19
  import { AXDecoratorModule } from '@acorex/components/decorators';
21
20
  import * as i3 from '@acorex/components/loading';
@@ -23,6 +22,7 @@ import { AXLoadingModule } from '@acorex/components/loading';
23
22
  import { AXBasePageComponent } from '@acorex/components/page';
24
23
  import * as i6 from '@acorex/core/translation';
25
24
  import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
25
+ import { AXPUnsavedChangesConfirmService } from '@acorex/platform/common';
26
26
  import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
27
27
 
28
28
  //#region ---- Dialog Action Shortcut Utilities ----
@@ -75,9 +75,9 @@ function resolveDialogActionShortcuts(defaults, overrides) {
75
75
  if (overrides.length === 0) {
76
76
  return undefined;
77
77
  }
78
- return dedupeDialogActionShortcuts([...defaults, ...overrides]);
78
+ return flattenDialogActionShortcutChords([...defaults, ...overrides]);
79
79
  }
80
- return defaults.length ? [...defaults] : undefined;
80
+ return flattenDialogActionShortcutChords(defaults);
81
81
  }
82
82
  /**
83
83
  * Applies built-in footer shortcuts when actions are declared without `shortcuts`
@@ -95,6 +95,11 @@ function resolveConfiguredFooterActionShortcuts(command, explicit) {
95
95
  }
96
96
  return undefined;
97
97
  }
98
+ function flattenDialogActionShortcutChords(shortcuts) {
99
+ const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
100
+ const deduped = dedupeDialogActionShortcuts(chords);
101
+ return deduped.length ? deduped : undefined;
102
+ }
98
103
  function dedupeDialogActionShortcuts(shortcuts) {
99
104
  const seen = new Set();
100
105
  const result = [];
@@ -2555,8 +2560,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2555
2560
  }]
2556
2561
  }] });
2557
2562
 
2558
- const OVERLAY_CONTAINER_SELECTOR = '.ax-overlay-container';
2559
- const OVERLAY_PANE_SELECTOR = '.ax-overlay-pane';
2563
+ //#region ---- Contracts barrel ----
2564
+ //#endregion
2565
+
2560
2566
  /**
2561
2567
  * Returns true when a nested overlay is open and Esc should close it first.
2562
2568
  * Returns false when only the dialog shell is on top.
@@ -2578,52 +2584,6 @@ function hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay) {
2578
2584
  }
2579
2585
  return null;
2580
2586
  }
2581
- /**
2582
- * Returns true when the overlay element is rendered and visible in the viewport.
2583
- */
2584
- function isVisibleOverlayElement(element) {
2585
- if (!(element instanceof HTMLElement)) {
2586
- return false;
2587
- }
2588
- const style = getComputedStyle(element);
2589
- if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
2590
- return false;
2591
- }
2592
- const rect = element.getBoundingClientRect();
2593
- return rect.width > 0 && rect.height > 0;
2594
- }
2595
- /**
2596
- * Collects visible overlay containers in stacking order (later / higher z-index wins).
2597
- */
2598
- function getVisibleOverlayContainers(document) {
2599
- return Array.from(document.querySelectorAll(OVERLAY_CONTAINER_SELECTOR))
2600
- .filter(isVisibleOverlayElement)
2601
- .sort(compareOverlayStackOrder);
2602
- }
2603
- /**
2604
- * Compares two overlay containers by z-index, then DOM order.
2605
- */
2606
- function compareOverlayStackOrder(a, b) {
2607
- const za = Number(getComputedStyle(a).zIndex) || 0;
2608
- const zb = Number(getComputedStyle(b).zIndex) || 0;
2609
- if (za !== zb) {
2610
- return za - zb;
2611
- }
2612
- const position = a.compareDocumentPosition(b);
2613
- if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
2614
- return -1;
2615
- }
2616
- if (position & Node.DOCUMENT_POSITION_PRECEDING) {
2617
- return 1;
2618
- }
2619
- return 0;
2620
- }
2621
- /**
2622
- * Returns visible anchored overlay panes that belong to widget popovers, not the dialog shell.
2623
- */
2624
- function getNestedOverlayPanes(document, dialogOverlay) {
2625
- return Array.from(document.querySelectorAll(OVERLAY_PANE_SELECTOR)).filter((pane) => isVisibleOverlayElement(pane) && !dialogOverlay?.contains(pane));
2626
- }
2627
2587
  /**
2628
2588
  * DOM fallback until {@link AXOverlayService} exposes open-overlay queries.
2629
2589
  *
@@ -2631,15 +2591,14 @@ function getNestedOverlayPanes(document, dialogOverlay) {
2631
2591
  * Widget popovers (select, datetime, dropdown) use anchored containers with `.ax-overlay-pane`.
2632
2592
  */
2633
2593
  function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
2634
- const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
2635
- if (getNestedOverlayPanes(document, dialogOverlay).length > 0) {
2594
+ const dialogOverlay = findOverlayContainerAncestor(dialogHost);
2595
+ if (getNestedVisibleOverlayPanes(document, dialogOverlay).length > 0) {
2636
2596
  return true;
2637
2597
  }
2638
- const visibleContainers = getVisibleOverlayContainers(document);
2639
- if (!visibleContainers.length) {
2598
+ const topContainer = getTopVisibleOverlayContainer(document);
2599
+ if (!topContainer) {
2640
2600
  return false;
2641
2601
  }
2642
- const topContainer = visibleContainers[visibleContainers.length - 1];
2643
2602
  if (!dialogOverlay) {
2644
2603
  return topContainer.contains(dialogHost) === false;
2645
2604
  }
@@ -2650,7 +2609,7 @@ function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
2650
2609
  * should not run on the parent dialog.
2651
2610
  */
2652
2611
  function shouldDeferDialogShortcutsToNestedOverlay(document, dialogHost, overlayService) {
2653
- const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
2612
+ const dialogOverlay = findOverlayContainerAncestor(dialogHost);
2654
2613
  if (overlayService) {
2655
2614
  const fromService = hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay);
2656
2615
  if (fromService !== null) {
@@ -2687,10 +2646,6 @@ function shouldRouteOnActionCancelThroughDismissGate(confirmCloseWhenDirtyEnable
2687
2646
  return confirmCloseWhenDirtyEnabled === true;
2688
2647
  }
2689
2648
 
2690
- /** Idle period after the last context change before capturing the clean baseline. */
2691
- const DIALOG_DIRTY_BASELINE_IDLE_MS = 500;
2692
- /** Hard fallback when idle detection never settles. */
2693
- const DIALOG_DIRTY_BASELINE_FALLBACK_DELAY_MS = 2000;
2694
2649
  /** Debounce after widget count stabilizes before re-evaluating footer actions. */
2695
2650
  const DIALOG_WIDGET_COUNT_STABLE_DELAY_MS = 350;
2696
2651
  class AXPDialogRendererComponent extends AXBasePageComponent {
@@ -2700,17 +2655,17 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2700
2655
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
2701
2656
  this.commandService = inject(AXPCommandService);
2702
2657
  this.hookService = inject(AXPHookService, { optional: true });
2703
- this.dialogService = inject(AXDialogService);
2658
+ this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
2704
2659
  this.overlayService = inject(AXOverlayService);
2705
2660
  this.translationService = inject(AXTranslationService);
2706
2661
  this.document = inject(DOCUMENT);
2707
2662
  this.host = inject((ElementRef));
2708
2663
  /** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
2709
2664
  this.callbackInvoked = false;
2710
- /** True after the post-init baseline snapshot has been captured. */
2711
- this.dirtyBaselineCaptured = false;
2712
2665
  /** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
2713
2666
  this.skipNextOnClosingDirtyCheck = false;
2667
+ /** Blocks re-entrant {@link onClosing} while the first close gate is still resolving. */
2668
+ this.closeGateInProgress = false;
2714
2669
  this.destroyRef = inject(DestroyRef);
2715
2670
  this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
2716
2671
  // This will be set by the popup service automatically - same as dynamic-dialog
@@ -2733,7 +2688,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2733
2688
  return;
2734
2689
  }
2735
2690
  // Unsaved-changes confirm is modal — parent footer shortcuts must not run underneath it.
2736
- if (this.dismissConfirmPromise) {
2691
+ if (this.unsavedChangesConfirm.isConfirmPending()) {
2737
2692
  return;
2738
2693
  }
2739
2694
  if (shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService)) {
@@ -2784,36 +2739,15 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2784
2739
  this.aggregateAndEvaluateActions();
2785
2740
  }, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
2786
2741
  }, ...(ngDevMode ? [{ debugName: "#widgetActionsEffect" }] : /* istanbul ignore next */ []));
2787
- this.#dirtyBaselineEffect = effect(() => {
2788
- if (this.dirtyBaselineCaptured) {
2789
- return;
2790
- }
2791
- const store = this.getWidgetContextStore();
2792
- if (!store) {
2793
- return;
2794
- }
2795
- store.data();
2796
- this.widgetCoreService?.registeredWidgetsCount();
2797
- this.scheduleDirtyBaselineCapture();
2798
- }, ...(ngDevMode ? [{ debugName: "#dirtyBaselineEffect" }] : /* istanbul ignore next */ []));
2799
2742
  }
2800
2743
  //#endregion
2801
2744
  //#region ---- Lifecycle ----
2802
2745
  ngOnInit() {
2803
2746
  const initialContext = this.config?.context || {};
2804
2747
  this.context.set(initialContext);
2805
- this.dirtyBaselineFallbackTimer = setTimeout(() => {
2806
- this.captureDirtyBaselineIfNeeded();
2807
- }, DIALOG_DIRTY_BASELINE_FALLBACK_DELAY_MS);
2808
2748
  this.document.addEventListener('keydown', this.onDialogShortcutCapture, true);
2809
2749
  this.destroyRef.onDestroy(() => {
2810
2750
  this.document.removeEventListener('keydown', this.onDialogShortcutCapture, true);
2811
- if (this.dirtyBaselineIdleTimer) {
2812
- clearTimeout(this.dirtyBaselineIdleTimer);
2813
- }
2814
- if (this.dirtyBaselineFallbackTimer) {
2815
- clearTimeout(this.dirtyBaselineFallbackTimer);
2816
- }
2817
2751
  });
2818
2752
  void this.invokeLayoutContextChangedHooks();
2819
2753
  }
@@ -2824,25 +2758,31 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2824
2758
  * This is the only place that prompts for unsaved changes.
2825
2759
  */
2826
2760
  async onClosing(e) {
2827
- if (this.dismissConfirmPromise) {
2761
+ if (this.unsavedChangesConfirm.isConfirmPending() || this.closeGateInProgress) {
2828
2762
  e.cancel = true;
2829
2763
  return;
2830
2764
  }
2831
- if (this.skipNextOnClosingDirtyCheck) {
2832
- this.skipNextOnClosingDirtyCheck = false;
2833
- this.completeDismissResolve();
2834
- return;
2835
- }
2836
- if (!this.config?.confirmCloseWhenDirty?.enabled) {
2765
+ this.closeGateInProgress = true;
2766
+ try {
2767
+ if (this.skipNextOnClosingDirtyCheck) {
2768
+ this.skipNextOnClosingDirtyCheck = false;
2769
+ this.completeDismissResolve();
2770
+ return;
2771
+ }
2772
+ if (!this.config?.confirmCloseWhenDirty?.enabled) {
2773
+ this.completeDismissResolve();
2774
+ return;
2775
+ }
2776
+ if (!(await this.confirmDismissIfDirty())) {
2777
+ e.cancel = true;
2778
+ this.pendingDismissResolvePayload = undefined;
2779
+ return;
2780
+ }
2837
2781
  this.completeDismissResolve();
2838
- return;
2839
2782
  }
2840
- if (!(await this.confirmDismissIfDirty())) {
2841
- e.cancel = true;
2842
- this.pendingDismissResolvePayload = undefined;
2843
- return;
2783
+ finally {
2784
+ this.closeGateInProgress = false;
2844
2785
  }
2845
- this.completeDismissResolve();
2846
2786
  }
2847
2787
  /**
2848
2788
  * Footer cancel and other in-app dismiss actions. Routes through `super.close()` so the
@@ -2866,50 +2806,10 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2866
2806
  }
2867
2807
  //#endregion
2868
2808
  //#region ---- Dirty State ----
2869
- getWidgetContextStore() {
2870
- return this.layoutRenderer()?.getContainer()?.contextService;
2871
- }
2872
- scheduleDirtyBaselineCapture() {
2873
- if (this.dirtyBaselineCaptured) {
2874
- return;
2875
- }
2876
- if (this.dirtyBaselineIdleTimer) {
2877
- clearTimeout(this.dirtyBaselineIdleTimer);
2878
- }
2879
- this.dirtyBaselineIdleTimer = setTimeout(() => {
2880
- this.captureDirtyBaselineIfNeeded();
2881
- }, DIALOG_DIRTY_BASELINE_IDLE_MS);
2882
- }
2883
- /**
2884
- * Captures a dialog-local baseline once after widget init/normalization goes idle.
2885
- */
2886
- captureDirtyBaselineIfNeeded() {
2887
- if (this.dirtyBaselineCaptured) {
2888
- return;
2889
- }
2890
- const store = this.getWidgetContextStore();
2891
- if (!store) {
2892
- return;
2893
- }
2894
- const widgetCount = this.widgetCoreService?.registeredWidgetsCount() ?? 0;
2895
- if (widgetCount === 0) {
2896
- return;
2897
- }
2898
- const snapshot = store.data();
2899
- this.dirtyBaseline = captureFormContextBaseline(snapshot);
2900
- store.commitBaseline();
2901
- this.dirtyBaselineCaptured = true;
2902
- if (this.dirtyBaselineIdleTimer) {
2903
- clearTimeout(this.dirtyBaselineIdleTimer);
2904
- this.dirtyBaselineIdleTimer = undefined;
2905
- }
2906
- if (this.dirtyBaselineFallbackTimer) {
2907
- clearTimeout(this.dirtyBaselineFallbackTimer);
2908
- this.dirtyBaselineFallbackTimer = undefined;
2909
- }
2809
+ getWidgetContainer() {
2810
+ return this.layoutRenderer()?.getContainer();
2910
2811
  }
2911
2812
  #widgetActionsEffect;
2912
- #dirtyBaselineEffect;
2913
2813
  //#endregion
2914
2814
  handleContextChanged(event) {
2915
2815
  this.context.set(event);
@@ -2929,7 +2829,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2929
2829
  const payload = {
2930
2830
  sessionKey: this.contextChangedHooksSessionKey,
2931
2831
  getContext: () => {
2932
- const store = this.getWidgetContextStore();
2832
+ const store = this.getWidgetContainer()?.contextService;
2933
2833
  return (store?.data() ?? this.context() ?? {});
2934
2834
  },
2935
2835
  metadata: meta,
@@ -3037,43 +2937,29 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
3037
2937
  }
3038
2938
  isDialogDirty() {
3039
2939
  const confirmOptions = this.config?.confirmCloseWhenDirty;
3040
- if (!confirmOptions?.enabled || !this.dirtyBaselineCaptured) {
2940
+ if (!confirmOptions?.enabled) {
3041
2941
  return false;
3042
2942
  }
3043
- const store = this.getWidgetContextStore();
3044
- if (!store) {
2943
+ const container = this.getWidgetContainer();
2944
+ if (!container?.isSavedCommitted()) {
3045
2945
  return false;
3046
2946
  }
3047
- const current = store.data();
3048
- const baseline = this.dirtyBaseline ?? store.initial();
2947
+ const store = container.contextService;
3049
2948
  if (typeof confirmOptions.isDirty === 'function') {
3050
- return confirmOptions.isDirty(current, baseline);
2949
+ return confirmOptions.isDirty(store.data(), store.saved());
3051
2950
  }
3052
- return (store.isUserDirty() ||
3053
- (this.widgetCoreService?.hasDirtyWidgets() ?? false) ||
3054
- isFormContextDirty(current, baseline));
2951
+ return container.isFormDirty();
3055
2952
  }
3056
- async confirmDismissIfDirty() {
3057
- if (this.dismissConfirmPromise) {
3058
- return this.dismissConfirmPromise;
3059
- }
2953
+ confirmDismissIfDirty() {
3060
2954
  const confirmOptions = this.config?.confirmCloseWhenDirty;
3061
2955
  if (!confirmOptions?.enabled || !this.isDialogDirty()) {
3062
- return true;
3063
- }
3064
- this.dismissConfirmPromise = this.promptUnsavedChangesConfirm(confirmOptions);
3065
- try {
3066
- return await this.dismissConfirmPromise;
2956
+ return Promise.resolve(true);
3067
2957
  }
3068
- finally {
3069
- this.dismissConfirmPromise = undefined;
3070
- }
3071
- }
3072
- async promptUnsavedChangesConfirm(confirmOptions) {
3073
- const title = await this.translationService.translateAsync(confirmOptions.title ?? '@general:messages.unsaved-changes.title');
3074
- const message = await this.translationService.translateAsync(confirmOptions.message ?? '@general:messages.unsaved-changes.message');
3075
- const dialogResult = await this.dialogService.confirm(title, message, 'warning', 'horizontal', false, 'cancel');
3076
- return dialogResult.result === true;
2958
+ return this.unsavedChangesConfirm.confirmIfDirty(true, {
2959
+ enabled: confirmOptions.enabled,
2960
+ title: confirmOptions.title,
2961
+ message: confirmOptions.message,
2962
+ });
3077
2963
  }
3078
2964
  /** Whether the layout form should be validated before running this footer command. */
3079
2965
  shouldValidateBeforeAction(cmd) {