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

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 (51) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +10 -2
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
  4. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-common.mjs +3792 -1679
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-core.mjs +1112 -103
  8. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-builder.mjs +53 -170
  10. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-components.mjs +70 -46
  12. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
  14. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  15. package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
  16. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-layout-entity.mjs +341 -418
  18. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-views.mjs +675 -301
  20. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-widget-core.mjs +115 -74
  22. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  23. 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
  24. 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
  25. 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
  26. 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
  27. package/fesm2022/acorex-platform-layout-widgets.mjs +184 -655
  28. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  29. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  30. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  32. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-themes-default.mjs +76 -32
  34. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  35. package/package.json +1 -1
  36. package/types/acorex-platform-auth.d.ts +2 -0
  37. package/types/acorex-platform-common.d.ts +891 -259
  38. package/types/acorex-platform-core.d.ts +284 -40
  39. package/types/acorex-platform-layout-builder.d.ts +10 -22
  40. package/types/acorex-platform-layout-components.d.ts +9 -7
  41. package/types/acorex-platform-layout-entity.d.ts +37 -41
  42. package/types/acorex-platform-layout-views.d.ts +125 -67
  43. package/types/acorex-platform-layout-widget-core.d.ts +53 -61
  44. package/types/acorex-platform-layout-widgets.d.ts +33 -20
  45. package/types/acorex-platform-themes-default.d.ts +14 -4
  46. package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
  47. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
  48. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
  49. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
  50. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
  51. 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,6 @@ 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';
2560
2563
  /**
2561
2564
  * Returns true when a nested overlay is open and Esc should close it first.
2562
2565
  * Returns false when only the dialog shell is on top.
@@ -2578,52 +2581,6 @@ function hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay) {
2578
2581
  }
2579
2582
  return null;
2580
2583
  }
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
2584
  /**
2628
2585
  * DOM fallback until {@link AXOverlayService} exposes open-overlay queries.
2629
2586
  *
@@ -2631,15 +2588,14 @@ function getNestedOverlayPanes(document, dialogOverlay) {
2631
2588
  * Widget popovers (select, datetime, dropdown) use anchored containers with `.ax-overlay-pane`.
2632
2589
  */
2633
2590
  function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
2634
- const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
2635
- if (getNestedOverlayPanes(document, dialogOverlay).length > 0) {
2591
+ const dialogOverlay = findOverlayContainerAncestor(dialogHost);
2592
+ if (getNestedVisibleOverlayPanes(document, dialogOverlay).length > 0) {
2636
2593
  return true;
2637
2594
  }
2638
- const visibleContainers = getVisibleOverlayContainers(document);
2639
- if (!visibleContainers.length) {
2595
+ const topContainer = getTopVisibleOverlayContainer(document);
2596
+ if (!topContainer) {
2640
2597
  return false;
2641
2598
  }
2642
- const topContainer = visibleContainers[visibleContainers.length - 1];
2643
2599
  if (!dialogOverlay) {
2644
2600
  return topContainer.contains(dialogHost) === false;
2645
2601
  }
@@ -2650,7 +2606,7 @@ function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
2650
2606
  * should not run on the parent dialog.
2651
2607
  */
2652
2608
  function shouldDeferDialogShortcutsToNestedOverlay(document, dialogHost, overlayService) {
2653
- const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
2609
+ const dialogOverlay = findOverlayContainerAncestor(dialogHost);
2654
2610
  if (overlayService) {
2655
2611
  const fromService = hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay);
2656
2612
  if (fromService !== null) {
@@ -2687,10 +2643,6 @@ function shouldRouteOnActionCancelThroughDismissGate(confirmCloseWhenDirtyEnable
2687
2643
  return confirmCloseWhenDirtyEnabled === true;
2688
2644
  }
2689
2645
 
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
2646
  /** Debounce after widget count stabilizes before re-evaluating footer actions. */
2695
2647
  const DIALOG_WIDGET_COUNT_STABLE_DELAY_MS = 350;
2696
2648
  class AXPDialogRendererComponent extends AXBasePageComponent {
@@ -2700,17 +2652,17 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2700
2652
  this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
2701
2653
  this.commandService = inject(AXPCommandService);
2702
2654
  this.hookService = inject(AXPHookService, { optional: true });
2703
- this.dialogService = inject(AXDialogService);
2655
+ this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
2704
2656
  this.overlayService = inject(AXOverlayService);
2705
2657
  this.translationService = inject(AXTranslationService);
2706
2658
  this.document = inject(DOCUMENT);
2707
2659
  this.host = inject((ElementRef));
2708
2660
  /** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
2709
2661
  this.callbackInvoked = false;
2710
- /** True after the post-init baseline snapshot has been captured. */
2711
- this.dirtyBaselineCaptured = false;
2712
2662
  /** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
2713
2663
  this.skipNextOnClosingDirtyCheck = false;
2664
+ /** Blocks re-entrant {@link onClosing} while the first close gate is still resolving. */
2665
+ this.closeGateInProgress = false;
2714
2666
  this.destroyRef = inject(DestroyRef);
2715
2667
  this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
2716
2668
  // This will be set by the popup service automatically - same as dynamic-dialog
@@ -2733,7 +2685,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2733
2685
  return;
2734
2686
  }
2735
2687
  // Unsaved-changes confirm is modal — parent footer shortcuts must not run underneath it.
2736
- if (this.dismissConfirmPromise) {
2688
+ if (this.unsavedChangesConfirm.isConfirmPending()) {
2737
2689
  return;
2738
2690
  }
2739
2691
  if (shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService)) {
@@ -2784,36 +2736,15 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2784
2736
  this.aggregateAndEvaluateActions();
2785
2737
  }, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
2786
2738
  }, ...(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
2739
  }
2800
2740
  //#endregion
2801
2741
  //#region ---- Lifecycle ----
2802
2742
  ngOnInit() {
2803
2743
  const initialContext = this.config?.context || {};
2804
2744
  this.context.set(initialContext);
2805
- this.dirtyBaselineFallbackTimer = setTimeout(() => {
2806
- this.captureDirtyBaselineIfNeeded();
2807
- }, DIALOG_DIRTY_BASELINE_FALLBACK_DELAY_MS);
2808
2745
  this.document.addEventListener('keydown', this.onDialogShortcutCapture, true);
2809
2746
  this.destroyRef.onDestroy(() => {
2810
2747
  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
2748
  });
2818
2749
  void this.invokeLayoutContextChangedHooks();
2819
2750
  }
@@ -2824,25 +2755,31 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2824
2755
  * This is the only place that prompts for unsaved changes.
2825
2756
  */
2826
2757
  async onClosing(e) {
2827
- if (this.dismissConfirmPromise) {
2758
+ if (this.unsavedChangesConfirm.isConfirmPending() || this.closeGateInProgress) {
2828
2759
  e.cancel = true;
2829
2760
  return;
2830
2761
  }
2831
- if (this.skipNextOnClosingDirtyCheck) {
2832
- this.skipNextOnClosingDirtyCheck = false;
2833
- this.completeDismissResolve();
2834
- return;
2835
- }
2836
- if (!this.config?.confirmCloseWhenDirty?.enabled) {
2762
+ this.closeGateInProgress = true;
2763
+ try {
2764
+ if (this.skipNextOnClosingDirtyCheck) {
2765
+ this.skipNextOnClosingDirtyCheck = false;
2766
+ this.completeDismissResolve();
2767
+ return;
2768
+ }
2769
+ if (!this.config?.confirmCloseWhenDirty?.enabled) {
2770
+ this.completeDismissResolve();
2771
+ return;
2772
+ }
2773
+ if (!(await this.confirmDismissIfDirty())) {
2774
+ e.cancel = true;
2775
+ this.pendingDismissResolvePayload = undefined;
2776
+ return;
2777
+ }
2837
2778
  this.completeDismissResolve();
2838
- return;
2839
2779
  }
2840
- if (!(await this.confirmDismissIfDirty())) {
2841
- e.cancel = true;
2842
- this.pendingDismissResolvePayload = undefined;
2843
- return;
2780
+ finally {
2781
+ this.closeGateInProgress = false;
2844
2782
  }
2845
- this.completeDismissResolve();
2846
2783
  }
2847
2784
  /**
2848
2785
  * Footer cancel and other in-app dismiss actions. Routes through `super.close()` so the
@@ -2866,50 +2803,10 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2866
2803
  }
2867
2804
  //#endregion
2868
2805
  //#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
- }
2806
+ getWidgetContainer() {
2807
+ return this.layoutRenderer()?.getContainer();
2910
2808
  }
2911
2809
  #widgetActionsEffect;
2912
- #dirtyBaselineEffect;
2913
2810
  //#endregion
2914
2811
  handleContextChanged(event) {
2915
2812
  this.context.set(event);
@@ -2929,7 +2826,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2929
2826
  const payload = {
2930
2827
  sessionKey: this.contextChangedHooksSessionKey,
2931
2828
  getContext: () => {
2932
- const store = this.getWidgetContextStore();
2829
+ const store = this.getWidgetContainer()?.contextService;
2933
2830
  return (store?.data() ?? this.context() ?? {});
2934
2831
  },
2935
2832
  metadata: meta,
@@ -3037,43 +2934,29 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
3037
2934
  }
3038
2935
  isDialogDirty() {
3039
2936
  const confirmOptions = this.config?.confirmCloseWhenDirty;
3040
- if (!confirmOptions?.enabled || !this.dirtyBaselineCaptured) {
2937
+ if (!confirmOptions?.enabled) {
3041
2938
  return false;
3042
2939
  }
3043
- const store = this.getWidgetContextStore();
3044
- if (!store) {
2940
+ const container = this.getWidgetContainer();
2941
+ if (!container?.isSavedCommitted()) {
3045
2942
  return false;
3046
2943
  }
3047
- const current = store.data();
3048
- const baseline = this.dirtyBaseline ?? store.initial();
2944
+ const store = container.contextService;
3049
2945
  if (typeof confirmOptions.isDirty === 'function') {
3050
- return confirmOptions.isDirty(current, baseline);
2946
+ return confirmOptions.isDirty(store.data(), store.saved());
3051
2947
  }
3052
- return (store.isUserDirty() ||
3053
- (this.widgetCoreService?.hasDirtyWidgets() ?? false) ||
3054
- isFormContextDirty(current, baseline));
2948
+ return container.isFormDirty();
3055
2949
  }
3056
- async confirmDismissIfDirty() {
3057
- if (this.dismissConfirmPromise) {
3058
- return this.dismissConfirmPromise;
3059
- }
2950
+ confirmDismissIfDirty() {
3060
2951
  const confirmOptions = this.config?.confirmCloseWhenDirty;
3061
2952
  if (!confirmOptions?.enabled || !this.isDialogDirty()) {
3062
- return true;
3063
- }
3064
- this.dismissConfirmPromise = this.promptUnsavedChangesConfirm(confirmOptions);
3065
- try {
3066
- return await this.dismissConfirmPromise;
2953
+ return Promise.resolve(true);
3067
2954
  }
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;
2955
+ return this.unsavedChangesConfirm.confirmIfDirty(true, {
2956
+ enabled: confirmOptions.enabled,
2957
+ title: confirmOptions.title,
2958
+ message: confirmOptions.message,
2959
+ });
3077
2960
  }
3078
2961
  /** Whether the layout form should be validated before running this footer command. */
3079
2962
  shouldValidateBeforeAction(cmd) {