@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
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, computed, Injectable, InjectionToken, inject, ElementRef, effect, untracked, EventEmitter, Injector, ChangeDetectorRef, ViewChild, Input, Output, ChangeDetectionStrategy, Component, input, output, ViewContainerRef, Directive, NgModule } from '@angular/core';
3
3
  import { convertArrayToDataSource, AXDataSource, AX_STYLE_COLOR_TYPES, AX_STYLE_LOOK_TYPES } from '@acorex/cdk/common';
4
- import { AXPContextStore, AXPDataSourceDefinitionProviderService, extractValue, AXPExpressionEvaluatorService, getSmart } from '@acorex/platform/core';
4
+ import { AXPContextStore, isSelectionValueEqual, isFormValueEqual, AXPDataSourceDefinitionProviderService, extractValue, AXPExpressionEvaluatorService, getSmart } from '@acorex/platform/core';
5
5
  export { normalizeDefinitionCategories } from '@acorex/platform/core';
6
6
  import { set, merge, cloneDeep, isNil, get, isEqual, isUndefined, isObjectLike, sum, isEmpty, isString } from 'lodash-es';
7
7
  import { Subject, BehaviorSubject, filter } from 'rxjs';
@@ -65,7 +65,6 @@ class AXPWidgetCoreService {
65
65
  return [AXPPageStatus.Processing, AXPPageStatus.Submitting, AXPPageStatus.Rendering].includes(this.status());
66
66
  }, ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
67
67
  this.registeredWidgetsCount = signal(0, ...(ngDevMode ? [{ debugName: "registeredWidgetsCount" }] : /* istanbul ignore next */ []));
68
- /** Bumped when a widget reports dirty-state changes via `api().isDirty()`. */
69
68
  this.dirtyWidgetsRevision = signal(0, ...(ngDevMode ? [{ debugName: "dirtyWidgetsRevision" }] : /* istanbul ignore next */ []));
70
69
  this.dirtyWidgetsRevisionSignal = this.dirtyWidgetsRevision.asReadonly();
71
70
  }
@@ -81,22 +80,19 @@ class AXPWidgetCoreService {
81
80
  }
82
81
  detectStatus() {
83
82
  const statuses = Array.from(this.widgets.values()).map((c) => c.status());
84
- // Rendering statuses
85
83
  if (statuses.some((status) => status === AXPWidgetStatus.Rendering)) {
86
84
  return AXPPageStatus.Rendering;
87
85
  }
88
86
  if (statuses.every((status) => status === AXPWidgetStatus.Rendered)) {
89
87
  return AXPPageStatus.Rendered;
90
88
  }
91
- // Processing statuses
92
89
  if (statuses.some((status) => status === AXPWidgetStatus.Processing)) {
93
90
  return AXPPageStatus.Processing;
94
91
  }
95
- // Error handling
96
92
  if (statuses.some((status) => status === AXPWidgetStatus.Error)) {
97
93
  return AXPPageStatus.Error;
98
94
  }
99
- return AXPPageStatus.Rendered; // Default to Loaded when all widgets are in a completed state
95
+ return AXPPageStatus.Rendered;
100
96
  }
101
97
  refresh() {
102
98
  setTimeout(() => {
@@ -131,11 +127,6 @@ class AXPWidgetCoreService {
131
127
  getWidget(id) {
132
128
  return this.widgets.get(id);
133
129
  }
134
- /**
135
- * Waits until a widget with the given id is registered, then resolves with it.
136
- * If the widget is already registered, resolves immediately.
137
- * Optionally accepts a timeout (in ms) after which it resolves with undefined.
138
- */
139
130
  async waitForWidget(id, timeoutMs) {
140
131
  const existing = this.widgets.get(id);
141
132
  if (existing) {
@@ -165,28 +156,12 @@ class AXPWidgetCoreService {
165
156
  }
166
157
  });
167
158
  }
168
- /**
169
- * Returns a list of registered widget ids (names).
170
- */
171
159
  listRegisteredWidgetNames() {
172
160
  return Array.from(this.widgets.keys());
173
161
  }
174
- /** Notifies listeners that a widget's composite dirty state may have changed. */
175
162
  notifyWidgetDirtyChanged() {
176
163
  this.dirtyWidgetsRevision.update((value) => value + 1);
177
164
  }
178
- /**
179
- * Returns whether any registered widget reports dirty state via `api().isDirty()`.
180
- */
181
- hasDirtyWidgets() {
182
- for (const widget of this.widgets.values()) {
183
- const isDirty = widget.api()?.['isDirty'];
184
- if (typeof isDirty === 'function' && isDirty()) {
185
- return true;
186
- }
187
- }
188
- return false;
189
- }
190
165
  ngOnDestroy() { }
191
166
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWidgetCoreService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
192
167
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWidgetCoreService }); }
@@ -503,7 +478,7 @@ class AXPValueWidgetComponent extends AXPLayoutBaseWidgetComponent {
503
478
  this.detectFullPath();
504
479
  // Set defaultValue if it exists and the path doesn't exist in context
505
480
  if (!isNil(this.defaultValue) && this.fullPath() && !this.contextService.hasValue(this.fullPath())) {
506
- this.setValue(this.defaultValue, { origin: 'system' });
481
+ this.setValue(this.defaultValue);
507
482
  }
508
483
  }
509
484
  //
@@ -516,31 +491,38 @@ class AXPValueWidgetComponent extends AXPLayoutBaseWidgetComponent {
516
491
  }
517
492
  return rawValue;
518
493
  }
519
- setValue(value, options) {
494
+ setValue(value) {
520
495
  if (this.node.valueTransforms?.setter) {
521
496
  value = this.node.valueTransforms?.setter(value);
522
497
  }
498
+ const path = this.fullPath();
499
+ if (!path) {
500
+ return;
501
+ }
523
502
  const oldValue = this.getValue();
524
503
  value = isUndefined(value) ? null : value;
525
504
  if (isNil(value) && isNil(oldValue)) {
526
505
  return;
527
506
  }
507
+ const savedAtPath = this.contextService.getSavedValue(path);
508
+ if (this.contextService.isSavedCommitted() &&
509
+ isSelectionValueEqual(value, savedAtPath) &&
510
+ !isEqual(oldValue, savedAtPath)) {
511
+ this.contextService.update(path, cloneDeep(savedAtPath));
512
+ this.onValueChanged.next({ sender: this });
513
+ return;
514
+ }
528
515
  // Reordered arrays must persist even when items are deep-equal (e.g. empty row objects).
529
516
  const isArrayReorder = Array.isArray(oldValue) &&
530
517
  Array.isArray(value) &&
531
518
  oldValue.length === value.length &&
532
519
  oldValue.some((v, i) => v !== value[i]);
533
- if (!isArrayReorder && isEqual(oldValue, value)) {
520
+ const isArrayLengthChange = Array.isArray(value) && (!Array.isArray(oldValue) || value.length !== oldValue.length);
521
+ if (!isArrayReorder && !isArrayLengthChange && isFormValueEqual(oldValue, value)) {
534
522
  return;
535
523
  }
536
- if (this.fullPath()) {
537
- this.contextService.update(this.fullPath(), value, { origin: options?.origin ?? 'user' });
538
- this.onValueChanged.next({ sender: this });
539
- }
540
- }
541
- /** Persists a user-initiated value change into the shared context store. */
542
- setUserValue(value) {
543
- this.setValue(value, { origin: 'user' });
524
+ this.contextService.update(path, value);
525
+ this.onValueChanged.next({ sender: this });
544
526
  }
545
527
  detectFullPath() {
546
528
  const sections = [];
@@ -578,7 +560,7 @@ class AXPValueWidgetComponent extends AXPLayoutBaseWidgetComponent {
578
560
  }
579
561
  handleValueChanged(e) {
580
562
  if (e.isUserInteraction) {
581
- this.setUserValue(e.value);
563
+ this.setValue(e.value);
582
564
  }
583
565
  }
584
566
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPValueWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -1335,11 +1317,16 @@ class AXPWidgetColumnRendererComponent extends AXDataTableColumnComponent {
1335
1317
  this.allowResizing = this.mergedOptions().allowResizing || true;
1336
1318
  this.allowSorting = this.sortEnabled;
1337
1319
  this.cdr.detectChanges();
1320
+ this.fixed = this.columnFixed;
1338
1321
  }
1339
1322
  ngOnChanges() {
1340
1323
  this.allowSorting = this.sortEnabled;
1341
1324
  this.sortOrder = this.headerSortDirection;
1342
1325
  this.sortIndex = this.headerSortPriority;
1326
+ this.fixed = this.columnFixed;
1327
+ if (this.customWidth) {
1328
+ this.width = this.customWidth;
1329
+ }
1343
1330
  }
1344
1331
  //#region ---- Performance Optimization Methods ----
1345
1332
  /**
@@ -1503,7 +1490,7 @@ class AXPWidgetColumnRendererComponent extends AXDataTableColumnComponent {
1503
1490
  return rowInjector;
1504
1491
  }
1505
1492
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWidgetColumnRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1506
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPWidgetColumnRendererComponent, isStandalone: false, selector: "axp-widget-column-renderer", inputs: { caption: "caption", customExpandIcon: "customExpandIcon", customCollapseIcon: "customCollapseIcon", customWidth: "customWidth", node: "node", footerTemplate: "footerTemplate", expandHandler: "expandHandler", sortEnabled: "sortEnabled", headerSortDirection: "headerSortDirection", headerSortPriority: "headerSortPriority", cellTemplate: "cellTemplate", headerTemplate: "headerTemplate" }, outputs: { sortToggle: "sortToggle" }, providers: [
1493
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPWidgetColumnRendererComponent, isStandalone: false, selector: "axp-widget-column-renderer", inputs: { caption: "caption", customExpandIcon: "customExpandIcon", customCollapseIcon: "customCollapseIcon", customWidth: "customWidth", node: "node", footerTemplate: "footerTemplate", expandHandler: "expandHandler", sortEnabled: "sortEnabled", headerSortDirection: "headerSortDirection", headerSortPriority: "headerSortPriority", columnFixed: "columnFixed", cellTemplate: "cellTemplate", headerTemplate: "headerTemplate" }, outputs: { sortToggle: "sortToggle" }, providers: [
1507
1494
  AXPWidgetCoreService,
1508
1495
  { provide: AXDataTableColumnComponent, useExisting: AXPWidgetColumnRendererComponent },
1509
1496
  ], viewQueries: [{ propertyName: "_contentFooterTemplate", first: true, predicate: ["footer"], descendants: true }, { propertyName: "_contentCellTemplate", first: true, predicate: ["cell"], descendants: true }, { propertyName: "_contentHeaderTemplate", first: true, predicate: ["header"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
@@ -1658,6 +1645,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1658
1645
  type: Input
1659
1646
  }], headerSortPriority: [{
1660
1647
  type: Input
1648
+ }], columnFixed: [{
1649
+ type: Input
1661
1650
  }], sortToggle: [{
1662
1651
  type: Output
1663
1652
  }], cellTemplate: [{
@@ -1672,13 +1661,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1672
1661
  args: ['header']
1673
1662
  }] } });
1674
1663
 
1664
+ /** Idle period after widgets register / hydrate before committing the saved baseline. */
1665
+ const FORM_SAVED_BASELINE_IDLE_MS = 500;
1675
1666
  class AXPWidgetContainerComponent {
1676
1667
  set context(value) {
1677
- // Parent re-binds the same context after widget-driven updates; avoid reset() clearing dirty paths.
1678
- if (isEqual(this.contextService.data(), value)) {
1679
- return;
1680
- }
1681
- this.contextService.set(value);
1668
+ this.syncExternalContext(value);
1682
1669
  }
1683
1670
  set functions(v) {
1684
1671
  this.builderService.setFunctions(v);
@@ -1687,25 +1674,38 @@ class AXPWidgetContainerComponent {
1687
1674
  this.contextService = inject(AXPContextStore);
1688
1675
  this.builderService = inject(AXPWidgetCoreService);
1689
1676
  this.onContextChanged = new EventEmitter();
1690
- /** True when user-edited context paths or composite widgets report dirty state. */
1677
+ this.isSavedCommitted = computed(() => this.contextService.isSavedCommitted(), ...(ngDevMode ? [{ debugName: "isSavedCommitted" }] : /* istanbul ignore next */ []));
1691
1678
  this.isFormDirty = computed(() => {
1692
- this.builderService.dirtyWidgetsRevisionSignal();
1693
- return this.contextService.isUserDirty() || this.builderService.hasDirtyWidgets();
1679
+ if (!this.contextService.isSavedCommitted()) {
1680
+ return false;
1681
+ }
1682
+ this.contextService.data();
1683
+ return this.contextService.isDirty();
1694
1684
  }, ...(ngDevMode ? [{ debugName: "isFormDirty" }] : /* istanbul ignore next */ []));
1695
- this.status = computed(() => {
1696
- return this.builderService.status();
1697
- }, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1698
- this.isBusy = computed(() => {
1699
- return this.builderService.isBusy();
1700
- }, ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
1685
+ this.status = computed(() => this.builderService.status(), ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1686
+ this.isBusy = computed(() => this.builderService.isBusy(), ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
1701
1687
  effect(() => {
1702
- if (this.contextService.isChanged()) {
1703
- const changeEvent = this.contextService.changeEvent();
1704
- this.onContextChanged.emit({
1705
- ...changeEvent,
1706
- isFormDirty: this.isFormDirty(),
1707
- });
1688
+ const isDirty = this.isFormDirty();
1689
+ const data = this.contextService.data();
1690
+ const changeEvent = this.contextService.changeEvent();
1691
+ if (!this.contextService.isChanged() && this.lastEmittedFormDirty === isDirty) {
1692
+ return;
1693
+ }
1694
+ this.lastEmittedFormDirty = isDirty;
1695
+ this.onContextChanged.emit({
1696
+ ...changeEvent,
1697
+ data: cloneDeep(data),
1698
+ isFormDirty: isDirty,
1699
+ });
1700
+ });
1701
+ effect(() => {
1702
+ if (this.contextService.isSavedCommitted()) {
1703
+ return;
1704
+ }
1705
+ if (this.builderService.registeredWidgetsCount() === 0) {
1706
+ return;
1708
1707
  }
1708
+ untracked(() => void this.settleSavedBaseline());
1709
1709
  });
1710
1710
  }
1711
1711
  refresh() {
@@ -1714,17 +1714,61 @@ class AXPWidgetContainerComponent {
1714
1714
  find(name) {
1715
1715
  return this.builderService.waitForWidget(name);
1716
1716
  }
1717
- /** Replaces context and clears dirty tracking (discard, save reload, external reset). */
1718
- replaceContext(value) {
1719
- const next = cloneDeep(value);
1720
- this.contextService.clearUserDirtyPaths();
1721
- // After save the parent often re-binds deeply-equal data; still reset the clean baseline.
1722
- if (isEqual(this.contextService.data(), next)) {
1723
- this.contextService.commitBaseline();
1724
- this.builderService.notifyWidgetDirtyChanged();
1717
+ syncExternalContext(value) {
1718
+ const next = cloneDeep(value ?? {});
1719
+ const current = this.contextService.data();
1720
+ if (isEqual(current, next)) {
1721
+ return;
1722
+ }
1723
+ if (this.contextService.isSavedCommitted() && this.isFormDirty()) {
1724
+ return;
1725
+ }
1726
+ if (this.contextService.isSavedCommitted()) {
1727
+ this.contextService.set(next);
1728
+ void this.settleSavedBaseline();
1729
+ return;
1730
+ }
1731
+ if (this.contextService.isChanged()) {
1725
1732
  return;
1726
1733
  }
1727
1734
  this.contextService.set(next);
1735
+ void this.settleSavedBaseline();
1736
+ }
1737
+ /** Commits the current data as saved after widget hydration settles. */
1738
+ settleSavedBaseline() {
1739
+ this.clearSavedBaselineTimer();
1740
+ return new Promise((resolve) => {
1741
+ const commit = () => {
1742
+ if (this.builderService.registeredWidgetsCount() === 0) {
1743
+ this.savedBaselineTimer = setTimeout(commit, FORM_SAVED_BASELINE_IDLE_MS);
1744
+ return;
1745
+ }
1746
+ this.contextService.commitSaved();
1747
+ this.lastEmittedFormDirty = undefined;
1748
+ this.savedBaselineTimer = undefined;
1749
+ resolve();
1750
+ };
1751
+ this.savedBaselineTimer = setTimeout(commit, FORM_SAVED_BASELINE_IDLE_MS);
1752
+ });
1753
+ }
1754
+ async replaceContext(value) {
1755
+ this.clearSavedBaselineTimer();
1756
+ this.contextService.set(cloneDeep(value));
1757
+ // Commit immediately so save/replace is clean even when `set()` no-ops (data already matches).
1758
+ this.contextService.commitSaved();
1759
+ this.lastEmittedFormDirty = undefined;
1760
+ await this.settleSavedBaseline();
1761
+ }
1762
+ revertAndSettle() {
1763
+ this.clearSavedBaselineTimer();
1764
+ this.contextService.revertToSaved();
1765
+ this.lastEmittedFormDirty = undefined;
1766
+ }
1767
+ clearSavedBaselineTimer() {
1768
+ if (this.savedBaselineTimer) {
1769
+ clearTimeout(this.savedBaselineTimer);
1770
+ this.savedBaselineTimer = undefined;
1771
+ }
1728
1772
  }
1729
1773
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWidgetContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1730
1774
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPWidgetContainerComponent, isStandalone: false, selector: "axp-widgets-container", inputs: { context: "context", functions: "functions" }, outputs: { onContextChanged: "onContextChanged" }, host: { styleAttribute: "display: contents;" }, providers: [AXPWidgetCoreService, AXPContextStore], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -1801,6 +1845,7 @@ class AXPWidgetRendererDirective {
1801
1845
  this.isVisible = signal(true, ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
1802
1846
  this.expressionEvaluators = new Map();
1803
1847
  this.renderTimeoutId = null;
1848
+ this.renderGeneration = 0;
1804
1849
  this.hasInitialRender = false;
1805
1850
  this.onContextChanged = new Subject();
1806
1851
  this.onLoadEvent = new Subject();
@@ -2159,18 +2204,18 @@ class AXPWidgetRendererDirective {
2159
2204
  }
2160
2205
  }
2161
2206
  rerenderComponent() {
2162
- // Clear any pending render operation to prevent double rendering
2163
2207
  if (this.renderTimeoutId) {
2164
2208
  clearTimeout(this.renderTimeoutId);
2165
2209
  this.renderTimeoutId = null;
2166
2210
  }
2167
- // Reset loading state to allow re-rendering
2168
- this.isLoading.set(false);
2169
- // Schedule the component loading
2211
+ const generation = ++this.renderGeneration;
2170
2212
  this.renderTimeoutId = setTimeout(async () => {
2171
- await this.loadComponent();
2172
2213
  this.renderTimeoutId = null;
2173
- });
2214
+ if (generation !== this.renderGeneration) {
2215
+ return;
2216
+ }
2217
+ await this.loadComponent();
2218
+ }, 0);
2174
2219
  }
2175
2220
  ngOnDestroy() {
2176
2221
  if (this.renderTimeoutId) {
@@ -2559,7 +2604,10 @@ class AXPWidgetRendererDirective {
2559
2604
  return this.contextService.data();
2560
2605
  },
2561
2606
  isDirty: () => {
2562
- return this.contextService.isUserDirty() || this.builderService.hasDirtyWidgets();
2607
+ if (!this.contextService.isSavedCommitted()) {
2608
+ return false;
2609
+ }
2610
+ return this.contextService.isDirty();
2563
2611
  },
2564
2612
  /**
2565
2613
  * Host/widget-under-edit `options` from the shared context store (same values as
@@ -3392,6 +3440,9 @@ var AXPWidgetGroupEnum;
3392
3440
  AXPWidgetGroupEnum["BaseWidget"] = "base-widget";
3393
3441
  })(AXPWidgetGroupEnum || (AXPWidgetGroupEnum = {}));
3394
3442
 
3443
+ //#region ---- Contracts barrel ----
3444
+ //#endregion
3445
+
3395
3446
  const AXP_STYLING_PROPERTY_GROUP = {
3396
3447
  name: 'styling',
3397
3448
  order: 1,