@memberjunction/ng-base-forms 6.1.0-edge.2 → 6.1.0-edge.3

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 (125) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +4 -1
  3. package/dist/lib/base-form-component.d.ts +93 -3
  4. package/dist/lib/base-form-component.d.ts.map +1 -1
  5. package/dist/lib/base-form-component.js +240 -9
  6. package/dist/lib/base-form-component.js.map +1 -1
  7. package/dist/lib/chrome/base-form-policy.d.ts +26 -0
  8. package/dist/lib/chrome/base-form-policy.d.ts.map +1 -0
  9. package/dist/lib/chrome/base-form-policy.js +9 -0
  10. package/dist/lib/chrome/base-form-policy.js.map +1 -0
  11. package/dist/lib/chrome/clipped-title.d.ts +8 -0
  12. package/dist/lib/chrome/clipped-title.d.ts.map +1 -0
  13. package/dist/lib/chrome/clipped-title.js +18 -0
  14. package/dist/lib/chrome/clipped-title.js.map +1 -0
  15. package/dist/lib/chrome/form-chrome-coordinator.service.d.ts +35 -0
  16. package/dist/lib/chrome/form-chrome-coordinator.service.d.ts.map +1 -0
  17. package/dist/lib/chrome/form-chrome-coordinator.service.js +96 -0
  18. package/dist/lib/chrome/form-chrome-coordinator.service.js.map +1 -0
  19. package/dist/lib/chrome/form-chrome-rail-pref.d.ts +24 -0
  20. package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -0
  21. package/dist/lib/chrome/form-chrome-rail-pref.js +52 -0
  22. package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -0
  23. package/dist/lib/chrome/form-chrome.d.ts +59 -0
  24. package/dist/lib/chrome/form-chrome.d.ts.map +1 -0
  25. package/dist/lib/chrome/form-chrome.js +37 -0
  26. package/dist/lib/chrome/form-chrome.js.map +1 -0
  27. package/dist/lib/chrome/load-form-chrome-rules.d.ts +8 -0
  28. package/dist/lib/chrome/load-form-chrome-rules.d.ts.map +1 -0
  29. package/dist/lib/chrome/load-form-chrome-rules.js +70 -0
  30. package/dist/lib/chrome/load-form-chrome-rules.js.map +1 -0
  31. package/dist/lib/chrome/resolve-form-chrome.d.ts +114 -0
  32. package/dist/lib/chrome/resolve-form-chrome.d.ts.map +1 -0
  33. package/dist/lib/chrome/resolve-form-chrome.js +713 -0
  34. package/dist/lib/chrome/resolve-form-chrome.js.map +1 -0
  35. package/dist/lib/container/record-form-container.component.d.ts +170 -6
  36. package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
  37. package/dist/lib/container/record-form-container.component.js +1194 -62
  38. package/dist/lib/container/record-form-container.component.js.map +1 -1
  39. package/dist/lib/explorer-entity-data-grid.component.d.ts +54 -1
  40. package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
  41. package/dist/lib/explorer-entity-data-grid.component.js +143 -7
  42. package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
  43. package/dist/lib/form-record-refresh.coordinator.d.ts +27 -0
  44. package/dist/lib/form-record-refresh.coordinator.d.ts.map +1 -0
  45. package/dist/lib/form-record-refresh.coordinator.js +33 -0
  46. package/dist/lib/form-record-refresh.coordinator.js.map +1 -0
  47. package/dist/lib/form-state.interface.d.ts +4 -0
  48. package/dist/lib/form-state.interface.d.ts.map +1 -1
  49. package/dist/lib/form-state.interface.js.map +1 -1
  50. package/dist/lib/form-state.service.d.ts +5 -0
  51. package/dist/lib/form-state.service.d.ts.map +1 -1
  52. package/dist/lib/form-state.service.js +23 -2
  53. package/dist/lib/form-state.service.js.map +1 -1
  54. package/dist/lib/host/entity-form-host.component.d.ts +5 -1
  55. package/dist/lib/host/entity-form-host.component.d.ts.map +1 -1
  56. package/dist/lib/host/entity-form-host.component.js +16 -2
  57. package/dist/lib/host/entity-form-host.component.js.map +1 -1
  58. package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts +6 -2
  59. package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts.map +1 -1
  60. package/dist/lib/isa-related-panel/isa-related-panel.component.js +15 -1
  61. package/dist/lib/isa-related-panel/isa-related-panel.component.js.map +1 -1
  62. package/dist/lib/panel/collapsible-panel.component.d.ts +24 -2
  63. package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
  64. package/dist/lib/panel/collapsible-panel.component.js +104 -10
  65. package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
  66. package/dist/lib/panel-slot/base-form-panel.d.ts +85 -1
  67. package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
  68. package/dist/lib/panel-slot/base-form-panel.js +72 -3
  69. package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
  70. package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts +8 -0
  71. package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts.map +1 -0
  72. package/dist/lib/panel-slot/collect-form-panel-registrations.js +19 -0
  73. package/dist/lib/panel-slot/collect-form-panel-registrations.js.map +1 -0
  74. package/dist/lib/panel-slot/form-contribution.d.ts +79 -0
  75. package/dist/lib/panel-slot/form-contribution.d.ts.map +1 -0
  76. package/dist/lib/panel-slot/form-contribution.js +253 -0
  77. package/dist/lib/panel-slot/form-contribution.js.map +1 -0
  78. package/dist/lib/panel-slot/form-contributions.component.d.ts +27 -0
  79. package/dist/lib/panel-slot/form-contributions.component.d.ts.map +1 -0
  80. package/dist/lib/panel-slot/form-contributions.component.js +83 -0
  81. package/dist/lib/panel-slot/form-contributions.component.js.map +1 -0
  82. package/dist/lib/panel-slot/form-panel-slot.component.d.ts +2 -0
  83. package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -1
  84. package/dist/lib/panel-slot/form-panel-slot.component.js +35 -4
  85. package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -1
  86. package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts +22 -0
  87. package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts.map +1 -0
  88. package/dist/lib/panel-slot/related-entity-grid-panel.component.js +78 -0
  89. package/dist/lib/panel-slot/related-entity-grid-panel.component.js.map +1 -0
  90. package/dist/lib/related-grid-height.d.ts +20 -0
  91. package/dist/lib/related-grid-height.d.ts.map +1 -0
  92. package/dist/lib/related-grid-height.js +30 -0
  93. package/dist/lib/related-grid-height.js.map +1 -0
  94. package/dist/lib/section-manager/section-manager.component.d.ts +20 -1
  95. package/dist/lib/section-manager/section-manager.component.d.ts.map +1 -1
  96. package/dist/lib/section-manager/section-manager.component.js +204 -25
  97. package/dist/lib/section-manager/section-manager.component.js.map +1 -1
  98. package/dist/lib/toolbar/form-toolbar.component.d.ts +55 -4
  99. package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
  100. package/dist/lib/toolbar/form-toolbar.component.js +734 -306
  101. package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
  102. package/dist/lib/types/form-events.d.ts +35 -0
  103. package/dist/lib/types/form-events.d.ts.map +1 -1
  104. package/dist/lib/types/form-events.js +45 -0
  105. package/dist/lib/types/form-events.js.map +1 -1
  106. package/dist/lib/types/form-toolbar-item.d.ts +145 -0
  107. package/dist/lib/types/form-toolbar-item.d.ts.map +1 -0
  108. package/dist/lib/types/form-toolbar-item.js +2 -0
  109. package/dist/lib/types/form-toolbar-item.js.map +1 -0
  110. package/dist/lib/types/form-types.d.ts +8 -0
  111. package/dist/lib/types/form-types.d.ts.map +1 -1
  112. package/dist/lib/types/form-types.js.map +1 -1
  113. package/dist/lib/types/toolbar-config.d.ts +7 -2
  114. package/dist/lib/types/toolbar-config.d.ts.map +1 -1
  115. package/dist/lib/types/toolbar-config.js +6 -0
  116. package/dist/lib/types/toolbar-config.js.map +1 -1
  117. package/dist/module.d.ts +17 -14
  118. package/dist/module.d.ts.map +1 -1
  119. package/dist/module.js +14 -0
  120. package/dist/module.js.map +1 -1
  121. package/dist/public-api.d.ts +13 -0
  122. package/dist/public-api.d.ts.map +1 -1
  123. package/dist/public-api.js +14 -0
  124. package/dist/public-api.js.map +1 -1
  125. package/package.json +19 -18
@@ -1,5 +1,9 @@
1
1
  import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectorRef, NgZone, inject } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { takeUntil } from 'rxjs/operators';
4
+ import { FormRecordRefreshCoordinator } from './form-record-refresh.coordinator';
2
5
  import { buildCompositeKey } from '@memberjunction/ng-entity-viewer';
6
+ import { RELATED_GRID_DEFAULT_MAX_PX, RelatedGridHeightPx } from './related-grid-height';
3
7
  import * as i0 from "@angular/core";
4
8
  import * as i1 from "@memberjunction/ng-entity-viewer";
5
9
  const _c0 = ["innerGrid"];
@@ -14,14 +18,78 @@ export class ExplorerEntityDataGridComponent {
14
18
  elementRef = inject(ElementRef);
15
19
  cdr = inject(ChangeDetectorRef);
16
20
  ngZone = inject(NgZone);
21
+ recordRefresh = inject(FormRecordRefreshCoordinator, { optional: true });
22
+ destroy$ = new Subject();
17
23
  // Pass-through inputs from EntityDataGridComponent
18
24
  Params = null;
19
25
  NewRecordValues = {};
20
26
  AllowLoad = true;
21
27
  ShowToolbar = true;
28
+ /**
29
+ * Existing generated forms still emit `[ShowToolbar]="false"` on related
30
+ * grids. Inside a related-entity panel we restore the toolbar so search /
31
+ * new / export / refresh come back without waiting for CodeGen.
32
+ */
33
+ get EffectiveShowToolbar() {
34
+ if (this.ShowToolbar)
35
+ return true;
36
+ return this.isInsideRelatedEntityPanel();
37
+ }
38
+ /**
39
+ * Related-entity accordion AND left-nav panels. Accordion used to leave
40
+ * Height='auto' (100% of an auto-sized parent) so AG Grid's viewport
41
+ * collapsed to 0 while the toolbar still showed "N rows".
42
+ */
43
+ isInsideRelatedEntityPanel() {
44
+ return this.findRelatedEntityPanel() != null;
45
+ }
46
+ findRelatedEntityPanel() {
47
+ let el = this.elementRef.nativeElement?.parentElement ?? null;
48
+ while (el) {
49
+ if (el.tagName?.toLowerCase() === 'mj-collapsible-panel'
50
+ && el.getAttribute('data-variant') === 'related-entity') {
51
+ return el;
52
+ }
53
+ el = el.parentElement;
54
+ }
55
+ return null;
56
+ }
57
+ /** Search box on the left of the toolbar. Default true. */
58
+ ShowSearch = true;
59
+ /** Forwarded to the inner grid. Defaults match `<mj-entity-data-grid>`. */
60
+ ShowNewButton = true;
61
+ ShowRefreshButton = true;
62
+ ShowExportButton = true;
63
+ ShowDeleteButton = false;
64
+ ShowCompareButton = false;
65
+ ShowMergeButton = false;
66
+ ShowAddToListButton = false;
67
+ ShowDuplicateSearchButton = false;
68
+ ShowCommunicationButton = false;
69
+ ShowRecycleBin = true;
22
70
  Height = 'auto';
71
+ /**
72
+ * When the grid sizes to its rows (left-nav related panels), this is the
73
+ * cap. Content taller than the cap scrolls inside AG Grid — it does not
74
+ * switch to paging. `null` means grow with the rows, no cap.
75
+ */
76
+ MaxHeight = RELATED_GRID_DEFAULT_MAX_PX;
23
77
  ToolbarConfig = {};
24
78
  SelectionMode = 'single';
79
+ /**
80
+ * Related-entity grids (accordion and left-nav) and explicit fit-content grids
81
+ * size to toolbar + header + rows instead of `height: 100%` of leftover / auto space.
82
+ */
83
+ sizedHeightPx = RelatedGridHeightPx(0, this.MaxHeight);
84
+ get ResolvedHeight() {
85
+ return this.shouldSizeToRows() ? this.sizedHeightPx : this.Height;
86
+ }
87
+ get hostHeightStyle() {
88
+ return this.shouldSizeToRows() ? `${this.sizedHeightPx}px` : '100%';
89
+ }
90
+ shouldSizeToRows() {
91
+ return this.Height === 'fit-content' || this.isInsideRelatedEntityPanel();
92
+ }
25
93
  /**
26
94
  * When true (default), the inner grid does not fetch until this component's host
27
95
  * element first scrolls into the viewport. This prevents related-entity grids on a
@@ -43,6 +111,7 @@ export class ExplorerEntityDataGridComponent {
43
111
  return this.AllowLoad && (!this.DeferLoadUntilVisible || this._hasBeenVisible);
44
112
  }
45
113
  ngAfterViewInit() {
114
+ this.subscribeToFormRefresh();
46
115
  if (!this.DeferLoadUntilVisible || typeof IntersectionObserver === 'undefined') {
47
116
  // Deferral off or unsupported environment — preserve eager-load behavior.
48
117
  this._hasBeenVisible = true;
@@ -62,9 +131,33 @@ export class ExplorerEntityDataGridComponent {
62
131
  });
63
132
  }
64
133
  ngOnDestroy() {
134
+ this.destroy$.next();
135
+ this.destroy$.complete();
65
136
  this._visibilityObserver?.disconnect();
66
137
  this._visibilityObserver = undefined;
67
138
  }
139
+ /**
140
+ * Reload this grid from the database. Used by the grid's own refresh
141
+ * button and by the parent-form refresh broadcast.
142
+ */
143
+ async Refresh() {
144
+ await this.innerGrid?.Refresh();
145
+ }
146
+ subscribeToFormRefresh() {
147
+ this.recordRefresh?.Refreshed$.pipe(takeUntil(this.destroy$)).subscribe(() => {
148
+ void this.refreshIfLoaded();
149
+ });
150
+ }
151
+ /**
152
+ * Parent-form refresh fan-out: only reload grids that have already paid
153
+ * for a RunView. Collapsed / never-seen related sections stay deferred.
154
+ */
155
+ async refreshIfLoaded() {
156
+ if (!this._hasBeenVisible) {
157
+ return;
158
+ }
159
+ await this.Refresh();
160
+ }
68
161
  onBecameVisible() {
69
162
  if (this._hasBeenVisible) {
70
163
  return;
@@ -111,8 +204,12 @@ export class ExplorerEntityDataGridComponent {
111
204
  this.AfterRowClick.emit(event);
112
205
  }
113
206
  onDataLoad(event) {
114
- // Re-emit the event for any consumers
115
207
  this.AfterDataLoad.emit(event);
208
+ if (!this.shouldSizeToRows()) {
209
+ return;
210
+ }
211
+ this.sizedHeightPx = RelatedGridHeightPx(event.loadedRowCount, this.MaxHeight);
212
+ this.cdr.markForCheck();
116
213
  }
117
214
  /**
118
215
  * The inner grid's "New" button bubbles up here. We re-emit as a
@@ -133,13 +230,15 @@ export class ExplorerEntityDataGridComponent {
133
230
  } if (rf & 2) {
134
231
  let _t;
135
232
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.innerGrid = _t.first);
136
- } }, inputs: { Params: "Params", NewRecordValues: "NewRecordValues", AllowLoad: "AllowLoad", ShowToolbar: "ShowToolbar", Height: "Height", ToolbarConfig: "ToolbarConfig", SelectionMode: "SelectionMode", DeferLoadUntilVisible: "DeferLoadUntilVisible", NavigateOnDoubleClick: "NavigateOnDoubleClick" }, outputs: { AfterRowDoubleClick: "AfterRowDoubleClick", AfterRowClick: "AfterRowClick", AfterDataLoad: "AfterDataLoad", Navigate: "Navigate" }, standalone: false, decls: 2, vars: 8, consts: [["innerGrid", ""], [3, "AfterRowDoubleClick", "AfterRowClick", "AfterDataLoad", "NewRecordTabRequested", "Params", "NewRecordValues", "AllowLoad", "ShowToolbar", "Height", "ToolbarConfig", "SelectionMode", "AllowColumnToggle"]], template: function ExplorerEntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
233
+ } }, hostVars: 2, hostBindings: function ExplorerEntityDataGridComponent_HostBindings(rf, ctx) { if (rf & 2) {
234
+ i0.ɵɵstyleProp("height", ctx.hostHeightStyle);
235
+ } }, inputs: { Params: "Params", NewRecordValues: "NewRecordValues", AllowLoad: "AllowLoad", ShowToolbar: "ShowToolbar", ShowSearch: "ShowSearch", ShowNewButton: "ShowNewButton", ShowRefreshButton: "ShowRefreshButton", ShowExportButton: "ShowExportButton", ShowDeleteButton: "ShowDeleteButton", ShowCompareButton: "ShowCompareButton", ShowMergeButton: "ShowMergeButton", ShowAddToListButton: "ShowAddToListButton", ShowDuplicateSearchButton: "ShowDuplicateSearchButton", ShowCommunicationButton: "ShowCommunicationButton", ShowRecycleBin: "ShowRecycleBin", Height: "Height", MaxHeight: "MaxHeight", ToolbarConfig: "ToolbarConfig", SelectionMode: "SelectionMode", DeferLoadUntilVisible: "DeferLoadUntilVisible", NavigateOnDoubleClick: "NavigateOnDoubleClick" }, outputs: { AfterRowDoubleClick: "AfterRowDoubleClick", AfterRowClick: "AfterRowClick", AfterDataLoad: "AfterDataLoad", Navigate: "Navigate" }, standalone: false, decls: 2, vars: 19, consts: [["innerGrid", ""], [3, "AfterRowDoubleClick", "AfterRowClick", "AfterDataLoad", "NewRecordTabRequested", "Params", "NewRecordValues", "AllowLoad", "ShowToolbar", "ShowSearch", "ShowNewButton", "ShowRefreshButton", "ShowExportButton", "ShowDeleteButton", "ShowCompareButton", "ShowMergeButton", "ShowAddToListButton", "ShowDuplicateSearchButton", "ShowCommunicationButton", "ShowRecycleBin", "Height", "ToolbarConfig", "SelectionMode", "AllowColumnToggle"]], template: function ExplorerEntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
137
236
  const _r1 = i0.ɵɵgetCurrentView();
138
237
  i0.ɵɵelementStart(0, "mj-entity-data-grid", 1, 0);
139
238
  i0.ɵɵlistener("AfterRowDoubleClick", function ExplorerEntityDataGridComponent_Template_mj_entity_data_grid_AfterRowDoubleClick_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onRowDoubleClick($event)); })("AfterRowClick", function ExplorerEntityDataGridComponent_Template_mj_entity_data_grid_AfterRowClick_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onRowClick($event)); })("AfterDataLoad", function ExplorerEntityDataGridComponent_Template_mj_entity_data_grid_AfterDataLoad_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onDataLoad($event)); })("NewRecordTabRequested", function ExplorerEntityDataGridComponent_Template_mj_entity_data_grid_NewRecordTabRequested_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onNewRecordTabRequested($event)); });
140
239
  i0.ɵɵelementEnd();
141
240
  } if (rf & 2) {
142
- i0.ɵɵproperty("Params", ctx.Params)("NewRecordValues", ctx.NewRecordValues)("AllowLoad", ctx.EffectiveAllowLoad)("ShowToolbar", ctx.ShowToolbar)("Height", ctx.Height)("ToolbarConfig", ctx.ToolbarConfig)("SelectionMode", ctx.SelectionMode)("AllowColumnToggle", false);
241
+ i0.ɵɵproperty("Params", ctx.Params)("NewRecordValues", ctx.NewRecordValues)("AllowLoad", ctx.EffectiveAllowLoad)("ShowToolbar", ctx.EffectiveShowToolbar)("ShowSearch", ctx.ShowSearch)("ShowNewButton", ctx.ShowNewButton)("ShowRefreshButton", ctx.ShowRefreshButton)("ShowExportButton", ctx.ShowExportButton)("ShowDeleteButton", ctx.ShowDeleteButton)("ShowCompareButton", ctx.ShowCompareButton)("ShowMergeButton", ctx.ShowMergeButton)("ShowAddToListButton", ctx.ShowAddToListButton)("ShowDuplicateSearchButton", ctx.ShowDuplicateSearchButton)("ShowCommunicationButton", ctx.ShowCommunicationButton)("ShowRecycleBin", ctx.ShowRecycleBin)("Height", ctx.ResolvedHeight)("ToolbarConfig", ctx.ToolbarConfig)("SelectionMode", ctx.SelectionMode)("AllowColumnToggle", false);
143
242
  } }, dependencies: [i1.EntityDataGridComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n }"] });
144
243
  }
145
244
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ExplorerEntityDataGridComponent, [{
@@ -152,8 +251,19 @@ export class ExplorerEntityDataGridComponent {
152
251
  [Params]="Params"
153
252
  [NewRecordValues]="NewRecordValues"
154
253
  [AllowLoad]="EffectiveAllowLoad"
155
- [ShowToolbar]="ShowToolbar"
156
- [Height]="Height"
254
+ [ShowToolbar]="EffectiveShowToolbar"
255
+ [ShowSearch]="ShowSearch"
256
+ [ShowNewButton]="ShowNewButton"
257
+ [ShowRefreshButton]="ShowRefreshButton"
258
+ [ShowExportButton]="ShowExportButton"
259
+ [ShowDeleteButton]="ShowDeleteButton"
260
+ [ShowCompareButton]="ShowCompareButton"
261
+ [ShowMergeButton]="ShowMergeButton"
262
+ [ShowAddToListButton]="ShowAddToListButton"
263
+ [ShowDuplicateSearchButton]="ShowDuplicateSearchButton"
264
+ [ShowCommunicationButton]="ShowCommunicationButton"
265
+ [ShowRecycleBin]="ShowRecycleBin"
266
+ [Height]="ResolvedHeight"
157
267
  [ToolbarConfig]="ToolbarConfig"
158
268
  [SelectionMode]="SelectionMode"
159
269
  [AllowColumnToggle]="false"
@@ -162,7 +272,9 @@ export class ExplorerEntityDataGridComponent {
162
272
  (AfterDataLoad)="onDataLoad($event)"
163
273
  (NewRecordTabRequested)="onNewRecordTabRequested($event)">
164
274
  </mj-entity-data-grid>
165
- `, styles: ["\n :host {\n display: block;\n height: 100%;\n width: 100%;\n }\n "] }]
275
+ `, host: {
276
+ '[style.height]': 'hostHeightStyle',
277
+ }, styles: ["\n :host {\n display: block;\n height: 100%;\n width: 100%;\n }\n "] }]
166
278
  }], null, { innerGrid: [{
167
279
  type: ViewChild,
168
280
  args: ['innerGrid']
@@ -174,8 +286,32 @@ export class ExplorerEntityDataGridComponent {
174
286
  type: Input
175
287
  }], ShowToolbar: [{
176
288
  type: Input
289
+ }], ShowSearch: [{
290
+ type: Input
291
+ }], ShowNewButton: [{
292
+ type: Input
293
+ }], ShowRefreshButton: [{
294
+ type: Input
295
+ }], ShowExportButton: [{
296
+ type: Input
297
+ }], ShowDeleteButton: [{
298
+ type: Input
299
+ }], ShowCompareButton: [{
300
+ type: Input
301
+ }], ShowMergeButton: [{
302
+ type: Input
303
+ }], ShowAddToListButton: [{
304
+ type: Input
305
+ }], ShowDuplicateSearchButton: [{
306
+ type: Input
307
+ }], ShowCommunicationButton: [{
308
+ type: Input
309
+ }], ShowRecycleBin: [{
310
+ type: Input
177
311
  }], Height: [{
178
312
  type: Input
313
+ }], MaxHeight: [{
314
+ type: Input
179
315
  }], ToolbarConfig: [{
180
316
  type: Input
181
317
  }], SelectionMode: [{
@@ -193,5 +329,5 @@ export class ExplorerEntityDataGridComponent {
193
329
  }], Navigate: [{
194
330
  type: Output
195
331
  }] }); })();
196
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ExplorerEntityDataGridComponent, { className: "ExplorerEntityDataGridComponent", filePath: "src/lib/explorer-entity-data-grid.component.ts", lineNumber: 51 }); })();
332
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ExplorerEntityDataGridComponent, { className: "ExplorerEntityDataGridComponent", filePath: "src/lib/explorer-entity-data-grid.component.ts", lineNumber: 69 }); })();
197
333
  //# sourceMappingURL=explorer-entity-data-grid.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"explorer-entity-data-grid.component.js","sourceRoot":"","sources":["../../src/lib/explorer-entity-data-grid.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAA4B,MAAM,EAAE,MAAM,eAAe,CAAC;AAE3J,OAAO,EAOH,iBAAiB,EACpB,MAAM,kCAAkC,CAAC;;;;AAI1C;;;;;GAKG;AA+BH,MAAM,OAAO,+BAA+B;IAChB,SAAS,CAA2B;IAEpD,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAChC,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAEhC,mDAAmD;IAC1C,MAAM,GAAyB,IAAI,CAAC;IACpC,eAAe,GAA4B,EAAE,CAAC;IAC9C,SAAS,GAAY,IAAI,CAAC;IAC1B,WAAW,GAAY,IAAI,CAAC;IAC5B,MAAM,GAAoC,MAAM,CAAC;IACjD,aAAa,GAAsB,EAAE,CAAC;IACtC,aAAa,GAAsB,QAAQ,CAAC;IAErD;;;;;;;;OAQG;IACM,qBAAqB,GAAY,IAAI,CAAC;IAE/C,qGAAqG;IAC7F,eAAe,GAAG,KAAK,CAAC;IACxB,mBAAmB,CAAwB;IAEnD;;;OAGG;IACH,IAAI,kBAAkB;QAClB,OAAO,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC;IACnF,CAAC;IAED,eAAe;QACX,IAAI,CAAC,IAAI,CAAC,qBAAqB,IAAI,OAAO,oBAAoB,KAAK,WAAW,EAAE,CAAC;YAC7E,0EAA0E;YAC1E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC5B,OAAO;QACX,CAAC;QAED,4EAA4E;QAC5E,yEAAyE;QACzE,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,mBAAmB,GAAG,IAAI,oBAAoB,CAC/C,CAAC,OAAO,EAAE,EAAE;gBACR,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC;oBACtC,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC3B,CAAC;YACL,CAAC;YACD,qFAAqF;YACrF,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CACpD,CAAC;YACF,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;IACP,CAAC;IAED,WAAW;QACP,IAAI,CAAC,mBAAmB,EAAE,UAAU,EAAE,CAAC;QACvC,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC;IACzC,CAAC;IAEO,eAAe;QACnB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QACD,wDAAwD;QACxD,IAAI,CAAC,mBAAmB,EAAE,UAAU,EAAE,CAAC;QACvC,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;YACjB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC5B,0FAA0F;YAC1F,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;OAGG;IACM,qBAAqB,GAAY,IAAI,CAAC;IAE/C,6CAA6C;IACnC,mBAAmB,GAAG,IAAI,YAAY,EAAgC,CAAC;IACvE,aAAa,GAAG,IAAI,YAAY,EAA0B,CAAC;IAC3D,aAAa,GAAG,IAAI,YAAY,EAA0B,CAAC;IAErE,6EAA6E;IACnE,QAAQ,GAAG,IAAI,YAAY,EAAuB,CAAC;IAE7D,gBAAgB,CAAC,KAAmC;QAChD,sCAAsC;QACtC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAErC,mCAAmC;QACnC,IAAI,IAAI,CAAC,qBAAqB,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YAC1C,yFAAyF;YACzF,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;YAC9C,IAAI,CAAC,UAAU;gBAAE,OAAO;YAExB,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAEtD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,UAAU,CAAC,IAAI;gBAC3B,UAAU,EAAE,IAAI;aACnB,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,UAAU,CAAC,KAA6B;QACpC,sCAAsC;QACtC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,UAAU,CAAC,KAA6B;QACpC,sCAAsC;QACtC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;;;;OAKG;IACH,uBAAuB,CAAC,KAAyE;QAC7F,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,YAAY;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI;YACjC,aAAa,EAAE,KAAK,CAAC,aAAa;SACrC,CAAC,CAAC;IACP,CAAC;yHAzIQ,+BAA+B;6DAA/B,+BAA+B;;;;;;;YAxBpC,iDAa8D;YAA1D,AADA,AADA,AADA,iMAAuB,4BAAwB,KAAC,wKAC/B,sBAAkB,KAAC,wKACnB,sBAAkB,KAAC,wLACX,mCAA+B,KAAC;YAC7D,iBAAsB;;YALlB,AADA,AADA,AADA,AADA,AADA,AADA,AADA,mCAAiB,wCACkB,qCACH,gCACL,sBACV,oCACc,oCACA,4BACJ;;;iFAe1B,+BAA+B;cA9B3C,SAAS;6BACI,KAAK,YACL,8BAA8B,YAC9B;;;;;;;;;;;;;;;;;;KAkBT;;kBAUA,SAAS;mBAAC,WAAW;;kBAOrB,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBAWL,KAAK;;kBA4DL,KAAK;;kBAGL,MAAM;;kBACN,MAAM;;kBACN,MAAM;;kBAGN,MAAM;;kFA7FE,+BAA+B","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectorRef, NgZone, AfterViewInit, OnDestroy, inject } from '@angular/core';\nimport { RunViewParams } from '@memberjunction/core';\nimport {\n EntityDataGridComponent,\n AfterRowDoubleClickEventArgs,\n AfterRowClickEventArgs,\n AfterDataLoadEventArgs,\n GridToolbarConfig,\n GridSelectionMode,\n buildCompositeKey\n} from '@memberjunction/ng-entity-viewer';\nimport { EntityInfo } from '@memberjunction/core';\nimport { FormNavigationEvent } from './types/navigation-events';\n\n/**\n * Wrapper for EntityDataGridComponent that emits navigation events on row double-click.\n * The host application subscribes to Navigate events and maps them to its routing system.\n *\n * This component is used by CodeGen for related entity grids in forms.\n */\n@Component({\n standalone: false,\n selector: 'mj-explorer-entity-data-grid',\n template: `\n <!-- Related-entity / form-embedded grid: not a saved User View, so there's no canonical\n GridState to manage — suppress the \"Manage Columns\" chooser (AllowColumnToggle=false). -->\n <mj-entity-data-grid\n #innerGrid\n [Params]=\"Params\"\n [NewRecordValues]=\"NewRecordValues\"\n [AllowLoad]=\"EffectiveAllowLoad\"\n [ShowToolbar]=\"ShowToolbar\"\n [Height]=\"Height\"\n [ToolbarConfig]=\"ToolbarConfig\"\n [SelectionMode]=\"SelectionMode\"\n [AllowColumnToggle]=\"false\"\n (AfterRowDoubleClick)=\"onRowDoubleClick($event)\"\n (AfterRowClick)=\"onRowClick($event)\"\n (AfterDataLoad)=\"onDataLoad($event)\"\n (NewRecordTabRequested)=\"onNewRecordTabRequested($event)\">\n </mj-entity-data-grid>\n `,\n styles: [`\n :host {\n display: block;\n height: 100%;\n width: 100%;\n }\n `]\n})\nexport class ExplorerEntityDataGridComponent implements AfterViewInit, OnDestroy {\n @ViewChild('innerGrid') innerGrid!: EntityDataGridComponent;\n\n private elementRef = inject(ElementRef);\n private cdr = inject(ChangeDetectorRef);\n private ngZone = inject(NgZone);\n\n // Pass-through inputs from EntityDataGridComponent\n @Input() Params: RunViewParams | null = null;\n @Input() NewRecordValues: Record<string, unknown> = {};\n @Input() AllowLoad: boolean = true;\n @Input() ShowToolbar: boolean = true;\n @Input() Height: number | 'auto' | 'fit-content' = 'auto';\n @Input() ToolbarConfig: GridToolbarConfig = {};\n @Input() SelectionMode: GridSelectionMode = 'single';\n\n /**\n * When true (default), the inner grid does not fetch until this component's host\n * element first scrolls into the viewport. This prevents related-entity grids on a\n * form — which are always rendered in the DOM (the collapsible panel hides them via\n * CSS, not @if) — from firing a RunView on form open while they're collapsed or below\n * the fold. The first time the panel becomes visible (scrolled into view and expanded,\n * giving the host non-zero area), the grid loads with its normal spinner.\n * Set to false to restore eager loading for consumers that always want immediate data.\n */\n @Input() DeferLoadUntilVisible: boolean = true;\n\n /** True once the host has first intersected the viewport (or deferral is disabled / unsupported). */\n private _hasBeenVisible = false;\n private _visibilityObserver?: IntersectionObserver;\n\n /**\n * Effective AllowLoad passed to the inner grid: the form's AllowLoad AND\n * (deferral disabled OR the panel has been seen at least once).\n */\n get EffectiveAllowLoad(): boolean {\n return this.AllowLoad && (!this.DeferLoadUntilVisible || this._hasBeenVisible);\n }\n\n ngAfterViewInit(): void {\n if (!this.DeferLoadUntilVisible || typeof IntersectionObserver === 'undefined') {\n // Deferral off or unsupported environment — preserve eager-load behavior.\n this._hasBeenVisible = true;\n return;\n }\n\n // Observe outside Angular so scroll churn doesn't trigger change detection;\n // we re-enter the zone only on the one-shot \"became visible\" transition.\n this.ngZone.runOutsideAngular(() => {\n this._visibilityObserver = new IntersectionObserver(\n (entries) => {\n if (entries.some(e => e.isIntersecting)) {\n this.onBecameVisible();\n }\n },\n // Small positive rootMargin pre-loads just before the panel scrolls fully into view.\n { root: null, rootMargin: '200px', threshold: 0 }\n );\n this._visibilityObserver.observe(this.elementRef.nativeElement);\n });\n }\n\n ngOnDestroy(): void {\n this._visibilityObserver?.disconnect();\n this._visibilityObserver = undefined;\n }\n\n private onBecameVisible(): void {\n if (this._hasBeenVisible) {\n return;\n }\n // One-shot: once loaded we never need to observe again.\n this._visibilityObserver?.disconnect();\n this._visibilityObserver = undefined;\n this.ngZone.run(() => {\n this._hasBeenVisible = true;\n // Flip EffectiveAllowLoad → true, which drives the inner grid's AllowLoad setter to load.\n this.cdr.detectChanges();\n });\n }\n\n /**\n * When true, double-clicking a row emits a Navigate event.\n * Defaults to true.\n */\n @Input() NavigateOnDoubleClick: boolean = true;\n\n // Re-emit events for consumers who need them\n @Output() AfterRowDoubleClick = new EventEmitter<AfterRowDoubleClickEventArgs>();\n @Output() AfterRowClick = new EventEmitter<AfterRowClickEventArgs>();\n @Output() AfterDataLoad = new EventEmitter<AfterDataLoadEventArgs>();\n\n /** Emitted when a row is double-clicked and NavigateOnDoubleClick is true */\n @Output() Navigate = new EventEmitter<FormNavigationEvent>();\n\n onRowDoubleClick(event: AfterRowDoubleClickEventArgs): void {\n // Re-emit the event for any consumers\n this.AfterRowDoubleClick.emit(event);\n\n // Emit navigation event if enabled\n if (this.NavigateOnDoubleClick && event.row) {\n // Use the inner grid's resolved EntityInfo - works for both ViewID and EntityName params\n const entityInfo = this.innerGrid?.EntityInfo;\n if (!entityInfo) return;\n\n const pkey = buildCompositeKey(event.row, entityInfo);\n\n this.Navigate.emit({\n Kind: 'record',\n EntityName: entityInfo.Name,\n PrimaryKey: pkey\n });\n }\n }\n\n onRowClick(event: AfterRowClickEventArgs): void {\n // Re-emit the event for any consumers\n this.AfterRowClick.emit(event);\n }\n\n onDataLoad(event: AfterDataLoadEventArgs): void {\n // Re-emit the event for any consumers\n this.AfterDataLoad.emit(event);\n }\n\n /**\n * The inner grid's \"New\" button bubbles up here. We re-emit as a\n * `new-record` Navigate event so the host form (and Explorer's\n * SingleRecordComponent) can call NavigationService.OpenNewEntityRecord\n * and pre-populate the foreign-key fields from NewRecordValues.\n */\n onNewRecordTabRequested(event: { entityInfo: EntityInfo; defaultValues: Record<string, unknown> }): void {\n this.Navigate.emit({\n Kind: 'new-record',\n EntityName: event.entityInfo.Name,\n DefaultValues: event.defaultValues,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"explorer-entity-data-grid.component.js","sourceRoot":"","sources":["../../src/lib/explorer-entity-data-grid.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectorRef, NgZone, AfterViewInit, OnDestroy, inject } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { RunViewParams } from '@memberjunction/core';\nimport { FormRecordRefreshCoordinator } from './form-record-refresh.coordinator';\nimport {\n EntityDataGridComponent,\n AfterRowDoubleClickEventArgs,\n AfterRowClickEventArgs,\n AfterDataLoadEventArgs,\n GridToolbarConfig,\n GridSelectionMode,\n buildCompositeKey\n} from '@memberjunction/ng-entity-viewer';\nimport { EntityInfo } from '@memberjunction/core';\nimport { FormNavigationEvent } from './types/navigation-events';\nimport { RELATED_GRID_DEFAULT_MAX_PX, RelatedGridHeightPx } from './related-grid-height';\n\n/**\n * Wrapper for EntityDataGridComponent that emits navigation events on row double-click.\n * The host application subscribes to Navigate events and maps them to its routing system.\n *\n * This component is used by CodeGen for related entity grids in forms.\n */\n@Component({\n standalone: false,\n selector: 'mj-explorer-entity-data-grid',\n template: `\n <!-- Related-entity / form-embedded grid: not a saved User View, so there's no canonical\n GridState to manage — suppress the \"Manage Columns\" chooser (AllowColumnToggle=false). -->\n <mj-entity-data-grid\n #innerGrid\n [Params]=\"Params\"\n [NewRecordValues]=\"NewRecordValues\"\n [AllowLoad]=\"EffectiveAllowLoad\"\n [ShowToolbar]=\"EffectiveShowToolbar\"\n [ShowSearch]=\"ShowSearch\"\n [ShowNewButton]=\"ShowNewButton\"\n [ShowRefreshButton]=\"ShowRefreshButton\"\n [ShowExportButton]=\"ShowExportButton\"\n [ShowDeleteButton]=\"ShowDeleteButton\"\n [ShowCompareButton]=\"ShowCompareButton\"\n [ShowMergeButton]=\"ShowMergeButton\"\n [ShowAddToListButton]=\"ShowAddToListButton\"\n [ShowDuplicateSearchButton]=\"ShowDuplicateSearchButton\"\n [ShowCommunicationButton]=\"ShowCommunicationButton\"\n [ShowRecycleBin]=\"ShowRecycleBin\"\n [Height]=\"ResolvedHeight\"\n [ToolbarConfig]=\"ToolbarConfig\"\n [SelectionMode]=\"SelectionMode\"\n [AllowColumnToggle]=\"false\"\n (AfterRowDoubleClick)=\"onRowDoubleClick($event)\"\n (AfterRowClick)=\"onRowClick($event)\"\n (AfterDataLoad)=\"onDataLoad($event)\"\n (NewRecordTabRequested)=\"onNewRecordTabRequested($event)\">\n </mj-entity-data-grid>\n `,\n styles: [`\n :host {\n display: block;\n height: 100%;\n width: 100%;\n }\n `],\n host: {\n '[style.height]': 'hostHeightStyle',\n },\n})\nexport class ExplorerEntityDataGridComponent implements AfterViewInit, OnDestroy {\n @ViewChild('innerGrid') innerGrid!: EntityDataGridComponent;\n\n private elementRef = inject(ElementRef);\n private cdr = inject(ChangeDetectorRef);\n private ngZone = inject(NgZone);\n private recordRefresh = inject(FormRecordRefreshCoordinator, { optional: true });\n private destroy$ = new Subject<void>();\n\n // Pass-through inputs from EntityDataGridComponent\n @Input() Params: RunViewParams | null = null;\n @Input() NewRecordValues: Record<string, unknown> = {};\n @Input() AllowLoad: boolean = true;\n @Input() ShowToolbar: boolean = true;\n\n /**\n * Existing generated forms still emit `[ShowToolbar]=\"false\"` on related\n * grids. Inside a related-entity panel we restore the toolbar so search /\n * new / export / refresh come back without waiting for CodeGen.\n */\n get EffectiveShowToolbar(): boolean {\n if (this.ShowToolbar) return true;\n return this.isInsideRelatedEntityPanel();\n }\n\n /**\n * Related-entity accordion AND left-nav panels. Accordion used to leave\n * Height='auto' (100% of an auto-sized parent) so AG Grid's viewport\n * collapsed to 0 while the toolbar still showed \"N rows\".\n */\n private isInsideRelatedEntityPanel(): boolean {\n return this.findRelatedEntityPanel() != null;\n }\n\n private findRelatedEntityPanel(): HTMLElement | null {\n let el: HTMLElement | null = this.elementRef.nativeElement?.parentElement ?? null;\n while (el) {\n if (el.tagName?.toLowerCase() === 'mj-collapsible-panel'\n && el.getAttribute('data-variant') === 'related-entity') {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n }\n /** Search box on the left of the toolbar. Default true. */\n @Input() ShowSearch: boolean = true;\n /** Forwarded to the inner grid. Defaults match `<mj-entity-data-grid>`. */\n @Input() ShowNewButton: boolean = true;\n @Input() ShowRefreshButton: boolean = true;\n @Input() ShowExportButton: boolean = true;\n @Input() ShowDeleteButton: boolean = false;\n @Input() ShowCompareButton: boolean = false;\n @Input() ShowMergeButton: boolean = false;\n @Input() ShowAddToListButton: boolean = false;\n @Input() ShowDuplicateSearchButton: boolean = false;\n @Input() ShowCommunicationButton: boolean = false;\n @Input() ShowRecycleBin: boolean = true;\n @Input() Height: number | 'auto' | 'fit-content' = 'auto';\n /**\n * When the grid sizes to its rows (left-nav related panels), this is the\n * cap. Content taller than the cap scrolls inside AG Grid — it does not\n * switch to paging. `null` means grow with the rows, no cap.\n */\n @Input() MaxHeight: number | null = RELATED_GRID_DEFAULT_MAX_PX;\n @Input() ToolbarConfig: GridToolbarConfig = {};\n @Input() SelectionMode: GridSelectionMode = 'single';\n\n /**\n * Related-entity grids (accordion and left-nav) and explicit fit-content grids\n * size to toolbar + header + rows instead of `height: 100%` of leftover / auto space.\n */\n private sizedHeightPx = RelatedGridHeightPx(0, this.MaxHeight);\n\n get ResolvedHeight(): number | 'auto' | 'fit-content' {\n return this.shouldSizeToRows() ? this.sizedHeightPx : this.Height;\n }\n\n get hostHeightStyle(): string {\n return this.shouldSizeToRows() ? `${this.sizedHeightPx}px` : '100%';\n }\n\n private shouldSizeToRows(): boolean {\n return this.Height === 'fit-content' || this.isInsideRelatedEntityPanel();\n }\n\n /**\n * When true (default), the inner grid does not fetch until this component's host\n * element first scrolls into the viewport. This prevents related-entity grids on a\n * form — which are always rendered in the DOM (the collapsible panel hides them via\n * CSS, not @if) — from firing a RunView on form open while they're collapsed or below\n * the fold. The first time the panel becomes visible (scrolled into view and expanded,\n * giving the host non-zero area), the grid loads with its normal spinner.\n * Set to false to restore eager loading for consumers that always want immediate data.\n */\n @Input() DeferLoadUntilVisible: boolean = true;\n\n /** True once the host has first intersected the viewport (or deferral is disabled / unsupported). */\n private _hasBeenVisible = false;\n private _visibilityObserver?: IntersectionObserver;\n\n /**\n * Effective AllowLoad passed to the inner grid: the form's AllowLoad AND\n * (deferral disabled OR the panel has been seen at least once).\n */\n get EffectiveAllowLoad(): boolean {\n return this.AllowLoad && (!this.DeferLoadUntilVisible || this._hasBeenVisible);\n }\n\n ngAfterViewInit(): void {\n this.subscribeToFormRefresh();\n if (!this.DeferLoadUntilVisible || typeof IntersectionObserver === 'undefined') {\n // Deferral off or unsupported environment — preserve eager-load behavior.\n this._hasBeenVisible = true;\n return;\n }\n\n // Observe outside Angular so scroll churn doesn't trigger change detection;\n // we re-enter the zone only on the one-shot \"became visible\" transition.\n this.ngZone.runOutsideAngular(() => {\n this._visibilityObserver = new IntersectionObserver(\n (entries) => {\n if (entries.some(e => e.isIntersecting)) {\n this.onBecameVisible();\n }\n },\n // Small positive rootMargin pre-loads just before the panel scrolls fully into view.\n { root: null, rootMargin: '200px', threshold: 0 }\n );\n this._visibilityObserver.observe(this.elementRef.nativeElement);\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n this._visibilityObserver?.disconnect();\n this._visibilityObserver = undefined;\n }\n\n /**\n * Reload this grid from the database. Used by the grid's own refresh\n * button and by the parent-form refresh broadcast.\n */\n public async Refresh(): Promise<void> {\n await this.innerGrid?.Refresh();\n }\n\n private subscribeToFormRefresh(): void {\n this.recordRefresh?.Refreshed$.pipe(takeUntil(this.destroy$)).subscribe(() => {\n void this.refreshIfLoaded();\n });\n }\n\n /**\n * Parent-form refresh fan-out: only reload grids that have already paid\n * for a RunView. Collapsed / never-seen related sections stay deferred.\n */\n private async refreshIfLoaded(): Promise<void> {\n if (!this._hasBeenVisible) {\n return;\n }\n await this.Refresh();\n }\n\n private onBecameVisible(): void {\n if (this._hasBeenVisible) {\n return;\n }\n // One-shot: once loaded we never need to observe again.\n this._visibilityObserver?.disconnect();\n this._visibilityObserver = undefined;\n this.ngZone.run(() => {\n this._hasBeenVisible = true;\n // Flip EffectiveAllowLoad → true, which drives the inner grid's AllowLoad setter to load.\n this.cdr.detectChanges();\n });\n }\n\n /**\n * When true, double-clicking a row emits a Navigate event.\n * Defaults to true.\n */\n @Input() NavigateOnDoubleClick: boolean = true;\n\n // Re-emit events for consumers who need them\n @Output() AfterRowDoubleClick = new EventEmitter<AfterRowDoubleClickEventArgs>();\n @Output() AfterRowClick = new EventEmitter<AfterRowClickEventArgs>();\n @Output() AfterDataLoad = new EventEmitter<AfterDataLoadEventArgs>();\n\n /** Emitted when a row is double-clicked and NavigateOnDoubleClick is true */\n @Output() Navigate = new EventEmitter<FormNavigationEvent>();\n\n onRowDoubleClick(event: AfterRowDoubleClickEventArgs): void {\n // Re-emit the event for any consumers\n this.AfterRowDoubleClick.emit(event);\n\n // Emit navigation event if enabled\n if (this.NavigateOnDoubleClick && event.row) {\n // Use the inner grid's resolved EntityInfo - works for both ViewID and EntityName params\n const entityInfo = this.innerGrid?.EntityInfo;\n if (!entityInfo) return;\n\n const pkey = buildCompositeKey(event.row, entityInfo);\n\n this.Navigate.emit({\n Kind: 'record',\n EntityName: entityInfo.Name,\n PrimaryKey: pkey\n });\n }\n }\n\n onRowClick(event: AfterRowClickEventArgs): void {\n // Re-emit the event for any consumers\n this.AfterRowClick.emit(event);\n }\n\n onDataLoad(event: AfterDataLoadEventArgs): void {\n this.AfterDataLoad.emit(event);\n if (!this.shouldSizeToRows()) {\n return;\n }\n this.sizedHeightPx = RelatedGridHeightPx(event.loadedRowCount, this.MaxHeight);\n this.cdr.markForCheck();\n }\n\n /**\n * The inner grid's \"New\" button bubbles up here. We re-emit as a\n * `new-record` Navigate event so the host form (and Explorer's\n * SingleRecordComponent) can call NavigationService.OpenNewEntityRecord\n * and pre-populate the foreign-key fields from NewRecordValues.\n */\n onNewRecordTabRequested(event: { entityInfo: EntityInfo; defaultValues: Record<string, unknown> }): void {\n this.Navigate.emit({\n Kind: 'new-record',\n EntityName: event.entityInfo.Name,\n DefaultValues: event.defaultValues,\n });\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { BaseEntity } from '@memberjunction/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Per-form broadcast that the current record was reloaded from the database.
5
+ *
6
+ * Provided on `<mj-record-form-container>` (same pattern as
7
+ * {@link FormChromeCoordinator} / {@link FormSlotCoordinator}) so projected
8
+ * related-entity grids, slot-mounted panels, the IS-A side panel, and the
9
+ * toolbar can all see it. `providers` (not `viewProviders`) is what makes
10
+ * that visible to `ng-content`.
11
+ *
12
+ * Listeners MUST no-op when they have never loaded (e.g. a related grid still
13
+ * waiting on `DeferLoadUntilVisible`). Parent refresh is not a license to
14
+ * fan out RunView across every collapsed section.
15
+ */
16
+ export declare class FormRecordRefreshCoordinator {
17
+ private readonly refreshed$;
18
+ /** Emits the live parent record after a successful `RefreshRecord()`. */
19
+ readonly Refreshed$: import("rxjs").Observable<BaseEntity<unknown>>;
20
+ /**
21
+ * Push a successful parent-record refresh to in-form listeners.
22
+ */
23
+ Notify(record: BaseEntity): void;
24
+ static ɵfac: i0.ɵɵFactoryDeclaration<FormRecordRefreshCoordinator, never>;
25
+ static ɵprov: i0.ɵɵInjectableDeclaration<FormRecordRefreshCoordinator>;
26
+ }
27
+ //# sourceMappingURL=form-record-refresh.coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-record-refresh.coordinator.d.ts","sourceRoot":"","sources":["../../src/lib/form-record-refresh.coordinator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;;AAElD;;;;;;;;;;;;GAYG;AACH,qBACa,4BAA4B;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD,yEAAyE;IACzE,SAAgB,UAAU,iDAAkC;IAE5D;;OAEG;IACI,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;yCAT9B,4BAA4B;6CAA5B,4BAA4B;CAYxC"}
@@ -0,0 +1,33 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Per-form broadcast that the current record was reloaded from the database.
6
+ *
7
+ * Provided on `<mj-record-form-container>` (same pattern as
8
+ * {@link FormChromeCoordinator} / {@link FormSlotCoordinator}) so projected
9
+ * related-entity grids, slot-mounted panels, the IS-A side panel, and the
10
+ * toolbar can all see it. `providers` (not `viewProviders`) is what makes
11
+ * that visible to `ng-content`.
12
+ *
13
+ * Listeners MUST no-op when they have never loaded (e.g. a related grid still
14
+ * waiting on `DeferLoadUntilVisible`). Parent refresh is not a license to
15
+ * fan out RunView across every collapsed section.
16
+ */
17
+ export class FormRecordRefreshCoordinator {
18
+ refreshed$ = new Subject();
19
+ /** Emits the live parent record after a successful `RefreshRecord()`. */
20
+ Refreshed$ = this.refreshed$.asObservable();
21
+ /**
22
+ * Push a successful parent-record refresh to in-form listeners.
23
+ */
24
+ Notify(record) {
25
+ this.refreshed$.next(record);
26
+ }
27
+ static ɵfac = function FormRecordRefreshCoordinator_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormRecordRefreshCoordinator)(); };
28
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FormRecordRefreshCoordinator, factory: FormRecordRefreshCoordinator.ɵfac });
29
+ }
30
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormRecordRefreshCoordinator, [{
31
+ type: Injectable
32
+ }], null, null); })();
33
+ //# sourceMappingURL=form-record-refresh.coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-record-refresh.coordinator.js","sourceRoot":"","sources":["../../src/lib/form-record-refresh.coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;;AAG/B;;;;;;;;;;;;GAYG;AAEH,MAAM,OAAO,4BAA4B;IACpB,UAAU,GAAG,IAAI,OAAO,EAAc,CAAC;IAExD,yEAAyE;IACzD,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;IAE5D;;OAEG;IACI,MAAM,CAAC,MAAkB;QAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;sHAXQ,4BAA4B;gEAA5B,4BAA4B,WAA5B,4BAA4B;;iFAA5B,4BAA4B;cADxC,UAAU","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { BaseEntity } from '@memberjunction/core';\n\n/**\n * Per-form broadcast that the current record was reloaded from the database.\n *\n * Provided on `<mj-record-form-container>` (same pattern as\n * {@link FormChromeCoordinator} / {@link FormSlotCoordinator}) so projected\n * related-entity grids, slot-mounted panels, the IS-A side panel, and the\n * toolbar can all see it. `providers` (not `viewProviders`) is what makes\n * that visible to `ng-content`.\n *\n * Listeners MUST no-op when they have never loaded (e.g. a related grid still\n * waiting on `DeferLoadUntilVisible`). Parent refresh is not a license to\n * fan out RunView across every collapsed section.\n */\n@Injectable()\nexport class FormRecordRefreshCoordinator {\n private readonly refreshed$ = new Subject<BaseEntity>();\n\n /** Emits the live parent record after a successful `RefreshRecord()`. */\n public readonly Refreshed$ = this.refreshed$.asObservable();\n\n /**\n * Push a successful parent-record refresh to in-form listeners.\n */\n public Notify(record: BaseEntity): void {\n this.refreshed$.next(record);\n }\n}\n"]}
@@ -37,6 +37,10 @@ export interface FormState {
37
37
  widthModeExplicit?: boolean;
38
38
  /** Custom section ordering - array of sectionKeys in user's preferred order */
39
39
  sectionOrder?: string[];
40
+ /** User-moved sections that should live in More. */
41
+ moreSectionKeys?: string[];
42
+ /** User-moved sections that should stay first-class (out of More). */
43
+ firstClassSectionKeys?: string[];
40
44
  }
41
45
  /**
42
46
  * Default state for a new form (when no saved state exists)
@@ -1 +1 @@
1
- {"version":3,"file":"form-state.interface.d.ts","sourceRoot":"","sources":["../../src/lib/form-state.interface.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,+CAA+C;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,oGAAoG;IACpG,SAAS,EAAE,UAAU,GAAG,YAAY,CAAC;IACrC;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,gBAEnC,CAAC"}
1
+ {"version":3,"file":"form-state.interface.d.ts","sourceRoot":"","sources":["../../src/lib/form-state.interface.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,+CAA+C;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,oGAAoG;IACpG,SAAS,EAAE,UAAU,GAAG,YAAY,CAAC;IACrC;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,gBAEnC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-state.interface.js","sourceRoot":"","sources":["../../src/lib/form-state.interface.ts"],"names":[],"mappings":"AA0CA;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAc;IACzC,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE,KAAK;IACtB,SAAS,EAAE,UAAU;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqB;IACnD,UAAU,EAAE,IAAI;CACnB,CAAC","sourcesContent":["/**\n * State for an individual form section (panel)\n */\nexport interface FormSectionState {\n /**\n * Whether the section is currently expanded. Optional: `undefined` means the user has\n * never explicitly expanded/collapsed this section, so the section's seeded default\n * (passed by the caller / from `initSections()`) should govern. This must stay decoupled\n * from `panelHeight` — persisting a height must NOT imply an expansion state, otherwise\n * measuring a collapsed panel on load would silently mark it expanded.\n */\n isExpanded?: boolean;\n /** User-resized panel height in pixels (related-entity panels only) */\n panelHeight?: number;\n}\n\n/**\n * Complete form state for an entity, persisted to User Settings\n */\nexport interface FormState {\n /** Per-section state, keyed by sectionKey */\n sections: Record<string, FormSectionState>;\n /** Whether to show fields with empty values */\n showEmptyFields: boolean;\n /** Form width mode - 'centered' uses max-width constraint, 'full-width' uses all available space */\n widthMode: 'centered' | 'full-width';\n /**\n * Whether `widthMode` reflects an explicit user choice (via the toolbar\n * width-toggle button) vs. a default carried along in the serialized\n * blob. Only true when `setWidthMode` has been called. Callers that want\n * to honor a component-level default should check this flag before\n * using `widthMode` — see `BaseFormComponent.getFormWidthMode`.\n *\n * Optional because pre-existing persisted blobs predate this flag; they\n * are treated as non-explicit so custom forms with their own defaults\n * can take precedence on upgrade.\n */\n widthModeExplicit?: boolean;\n /** Custom section ordering - array of sectionKeys in user's preferred order */\n sectionOrder?: string[];\n}\n\n/**\n * Default state for a new form (when no saved state exists)\n */\nexport const DEFAULT_FORM_STATE: FormState = {\n sections: {},\n showEmptyFields: false,\n widthMode: 'centered'\n};\n\n/**\n * Default state for a new section (when section not yet in saved state)\n */\nexport const DEFAULT_SECTION_STATE: FormSectionState = {\n isExpanded: true\n};\n"]}
1
+ {"version":3,"file":"form-state.interface.js","sourceRoot":"","sources":["../../src/lib/form-state.interface.ts"],"names":[],"mappings":"AA8CA;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAc;IACzC,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE,KAAK;IACtB,SAAS,EAAE,UAAU;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqB;IACnD,UAAU,EAAE,IAAI;CACnB,CAAC","sourcesContent":["/**\n * State for an individual form section (panel)\n */\nexport interface FormSectionState {\n /**\n * Whether the section is currently expanded. Optional: `undefined` means the user has\n * never explicitly expanded/collapsed this section, so the section's seeded default\n * (passed by the caller / from `initSections()`) should govern. This must stay decoupled\n * from `panelHeight` — persisting a height must NOT imply an expansion state, otherwise\n * measuring a collapsed panel on load would silently mark it expanded.\n */\n isExpanded?: boolean;\n /** User-resized panel height in pixels (related-entity panels only) */\n panelHeight?: number;\n}\n\n/**\n * Complete form state for an entity, persisted to User Settings\n */\nexport interface FormState {\n /** Per-section state, keyed by sectionKey */\n sections: Record<string, FormSectionState>;\n /** Whether to show fields with empty values */\n showEmptyFields: boolean;\n /** Form width mode - 'centered' uses max-width constraint, 'full-width' uses all available space */\n widthMode: 'centered' | 'full-width';\n /**\n * Whether `widthMode` reflects an explicit user choice (via the toolbar\n * width-toggle button) vs. a default carried along in the serialized\n * blob. Only true when `setWidthMode` has been called. Callers that want\n * to honor a component-level default should check this flag before\n * using `widthMode` — see `BaseFormComponent.getFormWidthMode`.\n *\n * Optional because pre-existing persisted blobs predate this flag; they\n * are treated as non-explicit so custom forms with their own defaults\n * can take precedence on upgrade.\n */\n widthModeExplicit?: boolean;\n /** Custom section ordering - array of sectionKeys in user's preferred order */\n sectionOrder?: string[];\n /** User-moved sections that should live in More. */\n moreSectionKeys?: string[];\n /** User-moved sections that should stay first-class (out of More). */\n firstClassSectionKeys?: string[];\n}\n\n/**\n * Default state for a new form (when no saved state exists)\n */\nexport const DEFAULT_FORM_STATE: FormState = {\n sections: {},\n showEmptyFields: false,\n widthMode: 'centered'\n};\n\n/**\n * Default state for a new section (when section not yet in saved state)\n */\nexport const DEFAULT_SECTION_STATE: FormSectionState = {\n isExpanded: true\n};\n"]}
@@ -152,6 +152,11 @@ export declare class FormStateService {
152
152
  * @param entityName The entity name
153
153
  */
154
154
  resetSectionOrder(entityName: string): void;
155
+ getMoreSectionKeys(entityName: string): string[] | undefined;
156
+ setMoreSectionKeys(entityName: string, moreSectionKeys: string[]): void;
157
+ getFirstClassSectionKeys(entityName: string): string[] | undefined;
158
+ setFirstClassSectionKeys(entityName: string, firstClassSectionKeys: string[]): void;
159
+ setChromeMembership(entityName: string, moreSectionKeys: string[], firstClassSectionKeys: string[]): void;
155
160
  /**
156
161
  * Check if a custom section order exists for an entity.
157
162
  * @param entityName The entity name
@@ -1 +1 @@
1
- {"version":3,"file":"form-state.service.d.ts","sourceRoot":"","sources":["../../src/lib/form-state.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"form-state.service.d.ts","sourceRoot":"","sources":["../../src/lib/form-state.service.ts"],"names":[],"mappings":"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"}
@@ -280,8 +280,29 @@ export class FormStateService {
280
280
  resetSectionOrder(entityName) {
281
281
  const subject = this.getOrCreateSubject(entityName);
282
282
  const currentState = subject.value;
283
- const { sectionOrder: _, ...stateWithoutOrder } = currentState;
284
- subject.next(stateWithoutOrder);
283
+ const { sectionOrder: _order, moreSectionKeys: _more, firstClassSectionKeys: _first, ...stateWithoutChrome } = currentState;
284
+ subject.next(stateWithoutChrome);
285
+ this.queueSave(entityName);
286
+ }
287
+ getMoreSectionKeys(entityName) {
288
+ return this.getCurrentState(entityName).moreSectionKeys;
289
+ }
290
+ setMoreSectionKeys(entityName, moreSectionKeys) {
291
+ const subject = this.getOrCreateSubject(entityName);
292
+ subject.next({ ...subject.value, moreSectionKeys });
293
+ this.queueSave(entityName);
294
+ }
295
+ getFirstClassSectionKeys(entityName) {
296
+ return this.getCurrentState(entityName).firstClassSectionKeys;
297
+ }
298
+ setFirstClassSectionKeys(entityName, firstClassSectionKeys) {
299
+ const subject = this.getOrCreateSubject(entityName);
300
+ subject.next({ ...subject.value, firstClassSectionKeys });
301
+ this.queueSave(entityName);
302
+ }
303
+ setChromeMembership(entityName, moreSectionKeys, firstClassSectionKeys) {
304
+ const subject = this.getOrCreateSubject(entityName);
305
+ subject.next({ ...subject.value, moreSectionKeys, firstClassSectionKeys });
285
306
  this.queueSave(entityName);
286
307
  }
287
308
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"form-state.service.js","sourceRoot":"","sources":["../../src/lib/form-state.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { Metadata, IMetadataProvider } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { FormState, FormSectionState, DEFAULT_FORM_STATE, DEFAULT_SECTION_STATE } from './form-state.interface';\n\nconst SETTING_KEY_PREFIX = 'Form.State.';\n\n/**\n * Service for managing form state persistence per entity.\n * State is stored in the User Settings entity and shared reactively\n * across all form instances for the same entity.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class FormStateService {\n /** Cache of BehaviorSubjects per entity name */\n private stateCache = new Map<string, BehaviorSubject<FormState>>();\n\n /** Track which entities have been loaded from DB */\n private loadedEntities = new Set<string>();\n\n /** Track loading promises to prevent duplicate loads */\n private loadingPromises = new Map<string, Promise<void>>();\n\n /** Track which entities are currently in edit mode (saves suppressed) */\n private editingEntities = new Set<string>();\n\n private _provider: IMetadataProvider | null = null;\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n private get metadata(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n /**\n * Get the observable state for an entity.\n * Automatically loads from User Settings on first access.\n * @param entityName The entity name\n * @returns Observable of the form state\n */\n getState$(entityName: string): Observable<FormState> {\n return this.getOrCreateSubject(entityName).asObservable();\n }\n\n /**\n * Get the current state value for an entity.\n * @param entityName The entity name\n * @returns Current form state\n */\n getCurrentState(entityName: string): FormState {\n return this.getOrCreateSubject(entityName).value;\n }\n\n /**\n * Initialize state for an entity by loading from User Settings.\n * Call this when a form component initializes.\n * @param entityName The entity name\n */\n async initializeState(entityName: string): Promise<FormState> {\n // If already loaded, return current state\n if (this.loadedEntities.has(entityName)) {\n return this.getCurrentState(entityName);\n }\n\n // If currently loading, wait for that promise\n const existingPromise = this.loadingPromises.get(entityName);\n if (existingPromise) {\n await existingPromise;\n return this.getCurrentState(entityName);\n }\n\n // Start loading\n const loadPromise = this.loadState(entityName);\n this.loadingPromises.set(entityName, loadPromise);\n\n try {\n await loadPromise;\n this.loadedEntities.add(entityName);\n return this.getCurrentState(entityName);\n } finally {\n this.loadingPromises.delete(entityName);\n }\n }\n\n /**\n * Get section state, returning defaults if section doesn't exist yet.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @returns Section state with defaults applied\n */\n getSectionState(entityName: string, sectionKey: string): FormSectionState {\n const state = this.getCurrentState(entityName);\n return state.sections[sectionKey] || { ...DEFAULT_SECTION_STATE };\n }\n\n /**\n * Check if a section is expanded.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param defaultExpanded Optional default value to use when no persisted state exists (defaults to DEFAULT_SECTION_STATE.isExpanded)\n * @returns True if expanded\n */\n isSectionExpanded(entityName: string, sectionKey: string, defaultExpanded?: boolean): boolean {\n const state = this.getCurrentState(entityName);\n const sectionState = state.sections[sectionKey];\n // Only honor a persisted expansion when it was EXPLICITLY set. A section entry that\n // exists solely to hold a panelHeight has isExpanded === undefined and must fall through\n // to the caller's seeded default (collapsed for related-entity panels).\n if (sectionState && sectionState.isExpanded !== undefined) {\n return sectionState.isExpanded;\n }\n // No explicit persisted state - use provided default or fall back to DEFAULT_SECTION_STATE\n return defaultExpanded !== undefined ? defaultExpanded : (DEFAULT_SECTION_STATE.isExpanded ?? true);\n }\n\n /**\n * Set section expanded state.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param isExpanded Whether the section is expanded\n */\n setSectionExpanded(entityName: string, sectionKey: string, isExpanded: boolean): void {\n this.updateSectionState(entityName, sectionKey, { isExpanded });\n }\n\n /**\n * Get the persisted panel height for a section.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @returns Panel height in pixels, or undefined if no custom height is set\n */\n getSectionPanelHeight(entityName: string, sectionKey: string): number | undefined {\n const state = this.getCurrentState(entityName);\n return state.sections[sectionKey]?.panelHeight;\n }\n\n /**\n * Set the panel height for a section (persisted to User Settings).\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param height Panel height in pixels\n */\n setSectionPanelHeight(entityName: string, sectionKey: string, height: number): void {\n this.updateSectionState(entityName, sectionKey, { panelHeight: height });\n }\n\n /**\n * Toggle section expanded state.\n * @param entityName The entity name\n * @param sectionKey The section key\n */\n toggleSection(entityName: string, sectionKey: string): void {\n const current = this.isSectionExpanded(entityName, sectionKey);\n this.setSectionExpanded(entityName, sectionKey, !current);\n }\n\n /**\n * Get form width mode.\n * @param entityName The entity name\n * @returns Width mode ('centered' or 'full-width')\n */\n getWidthMode(entityName: string): 'centered' | 'full-width' {\n return this.getCurrentState(entityName).widthMode;\n }\n\n /**\n * Whether this entity has had an EXPLICIT widthMode set via the toolbar\n * width-toggle (and thus the user's choice should win over component\n * defaults). Returns false for pre-existing persisted blobs that had a\n * default `widthMode` serialized as a side effect of other state saves.\n */\n hasExplicitWidthMode(entityName: string): boolean {\n return this.getCurrentState(entityName).widthModeExplicit === true;\n }\n\n /**\n * Set form width mode. Marks the preference as explicit so it wins over\n * any component-level default in `BaseFormComponent.getFormWidthMode`.\n */\n setWidthMode(entityName: string, widthMode: 'centered' | 'full-width'): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n widthMode,\n widthModeExplicit: true\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Toggle form width mode between centered and full-width.\n * @param entityName The entity name\n */\n toggleWidthMode(entityName: string): void {\n const current = this.getWidthMode(entityName);\n this.setWidthMode(entityName, current === 'centered' ? 'full-width' : 'centered');\n }\n\n /**\n * Get showEmptyFields preference for an entity.\n * @param entityName The entity name\n * @returns Whether to show empty fields\n */\n getShowEmptyFields(entityName: string): boolean {\n return this.getCurrentState(entityName).showEmptyFields;\n }\n\n /**\n * Set showEmptyFields preference for an entity.\n * @param entityName The entity name\n * @param show Whether to show empty fields\n */\n setShowEmptyFields(entityName: string, show: boolean): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n showEmptyFields: show\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Expand all sections for an entity.\n * @param entityName The entity name\n * @param sectionKeys Array of all section keys to expand\n */\n expandAllSections(entityName: string, sectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newSections = { ...currentState.sections };\n\n for (const key of sectionKeys) {\n newSections[key] = {\n ...DEFAULT_SECTION_STATE,\n ...newSections[key],\n isExpanded: true\n };\n }\n\n subject.next({ ...currentState, sections: newSections });\n this.queueSave(entityName);\n }\n\n /**\n * Collapse all sections for an entity.\n * @param entityName The entity name\n * @param sectionKeys Array of all section keys to collapse\n */\n collapseAllSections(entityName: string, sectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newSections = { ...currentState.sections };\n\n for (const key of sectionKeys) {\n newSections[key] = {\n ...DEFAULT_SECTION_STATE,\n ...newSections[key],\n isExpanded: false\n };\n }\n\n subject.next({ ...currentState, sections: newSections });\n this.queueSave(entityName);\n }\n\n /**\n * Reset state to defaults for an entity.\n * @param entityName The entity name\n */\n resetToDefaults(entityName: string): void {\n const subject = this.getOrCreateSubject(entityName);\n subject.next({ ...DEFAULT_FORM_STATE });\n this.queueSave(entityName);\n }\n\n /**\n * Get the custom section order for an entity.\n * @param entityName The entity name\n * @returns Array of section keys in user's preferred order, or undefined if using default order\n */\n getSectionOrder(entityName: string): string[] | undefined {\n return this.getCurrentState(entityName).sectionOrder;\n }\n\n /**\n * Set the custom section order for an entity.\n * @param entityName The entity name\n * @param sectionOrder Array of section keys in the desired order\n */\n setSectionOrder(entityName: string, sectionOrder: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n sectionOrder\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Reset the section order to default (removes custom ordering).\n * @param entityName The entity name\n */\n resetSectionOrder(entityName: string): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const { sectionOrder: _, ...stateWithoutOrder } = currentState;\n subject.next(stateWithoutOrder as FormState);\n this.queueSave(entityName);\n }\n\n /**\n * Check if a custom section order exists for an entity.\n * @param entityName The entity name\n * @returns True if custom order exists\n */\n hasCustomSectionOrder(entityName: string): boolean {\n const order = this.getSectionOrder(entityName);\n return order !== undefined && order.length > 0;\n }\n\n /**\n * Set edit mode for an entity. While in edit mode, state changes update\n * the in-memory BehaviorSubject (so the UI stays reactive) but skip\n * database persistence. When edit mode ends, a single save is queued\n * to persist the final layout state.\n * @param entityName The entity name\n * @param editing Whether the form is entering (true) or exiting (false) edit mode\n */\n setEditMode(entityName: string, editing: boolean): void {\n if (editing) {\n this.editingEntities.add(entityName);\n } else {\n this.editingEntities.delete(entityName);\n // Persist the final state once on exit\n this.queueSave(entityName);\n }\n }\n\n /**\n * Get the count of expanded sections.\n * @param entityName The entity name\n * @param sectionKeys Array of section keys to check\n * @returns Number of expanded sections\n */\n getExpandedCount(entityName: string, sectionKeys: string[]): number {\n const state = this.getCurrentState(entityName);\n return sectionKeys.filter(key => {\n const section = state.sections[key];\n // No entry → seeded global default; entry with explicit value → that value;\n // entry with only a panelHeight (isExpanded undefined) → treat as collapsed.\n return section ? (section.isExpanded ?? false) : DEFAULT_SECTION_STATE.isExpanded;\n }).length;\n }\n\n // -------------------- Private Methods --------------------\n\n /**\n * Get or create the BehaviorSubject for an entity.\n */\n private getOrCreateSubject(entityName: string): BehaviorSubject<FormState> {\n let subject = this.stateCache.get(entityName);\n if (!subject) {\n subject = new BehaviorSubject<FormState>({ ...DEFAULT_FORM_STATE });\n this.stateCache.set(entityName, subject);\n }\n return subject;\n }\n\n /**\n * Update a single section's state.\n */\n private updateSectionState(entityName: string, sectionKey: string, updates: Partial<FormSectionState>): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n // Start from the existing entry or an EMPTY object — do NOT seed DEFAULT_SECTION_STATE.\n // Seeding it would force isExpanded:true onto a section that's only being given a\n // panelHeight, silently expanding panels the user never opened.\n const currentSection = currentState.sections[sectionKey] || {};\n\n const newState: FormState = {\n ...currentState,\n sections: {\n ...currentState.sections,\n [sectionKey]: {\n ...currentSection,\n ...updates\n }\n }\n };\n\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Generate the User Settings key for an entity.\n */\n private getSettingKey(entityName: string): string {\n return `${SETTING_KEY_PREFIX}${entityName}`;\n }\n\n /**\n * Load state from User Settings using UserInfoEngine for cached access.\n */\n private async loadState(entityName: string): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n return;\n }\n\n const settingKey = this.getSettingKey(entityName);\n const engine = UserInfoEngine.Instance;\n\n // Find setting from cached user settings\n const setting = engine.UserSettings.find(s => s.Setting === settingKey);\n\n const subject = this.getOrCreateSubject(entityName);\n\n if (setting?.Value) {\n const savedState = JSON.parse(setting.Value) as Partial<FormState>;\n // Merge with defaults to handle new properties. `widthModeExplicit`\n // rides along with the blob — pre-existing blobs won't have it,\n // which is the desired behavior (component defaults win).\n subject.next({ ...DEFAULT_FORM_STATE, ...savedState });\n } else {\n // No saved state, use defaults\n subject.next({ ...DEFAULT_FORM_STATE });\n }\n } catch (error) {\n console.warn(`Failed to load form state for ${entityName}:`, error);\n // Keep default state on error\n }\n }\n\n /**\n * Queue a debounced save using UserInfoEngine's centralized debounce.\n * Skipped when the entity is in edit mode to avoid persisting transient\n * layout changes made while editing a record.\n */\n private queueSave(entityName: string): void {\n if (this.editingEntities.has(entityName)) {\n return;\n }\n\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n return;\n }\n\n const settingKey = this.getSettingKey(entityName);\n const state = this.getCurrentState(entityName);\n UserInfoEngine.Instance.SetSettingDebounced(settingKey, JSON.stringify(state));\n }\n}\n"]}
1
+ {"version":3,"file":"form-state.service.js","sourceRoot":"","sources":["../../src/lib/form-state.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAc,MAAM,MAAM,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAqB,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAA+B,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;;AAEhH,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAEzC;;;;GAIG;AAIH,MAAM,OAAO,gBAAgB;IACzB,gDAAgD;IACxC,UAAU,GAAG,IAAI,GAAG,EAAsC,CAAC;IAEnE,oDAAoD;IAC5C,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IAE3C,wDAAwD;IAChD,eAAe,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE3D,yEAAyE;IACjE,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,SAAS,GAA6B,IAAI,CAAC;IAEnD;;OAEG;IACH,IAAW,QAAQ,CAAC,KAA+B;QAC/C,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED,IAAY,QAAQ;QAChB,OAAO,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAC/C,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,UAAkB;QACxB,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,YAAY,EAAE,CAAC;IAC9D,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,UAAkB;QAC9B,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CAAC,UAAkB;QACpC,0CAA0C;QAC1C,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,8CAA8C;QAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,eAAe,EAAE,CAAC;YAClB,MAAM,eAAe,CAAC;YACtB,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,gBAAgB;QAChB,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAElD,IAAI,CAAC;YACD,MAAM,WAAW,CAAC;YAClB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,UAAkB,EAAE,UAAkB;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,qBAAqB,EAAE,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,iBAAiB,CAAC,UAAkB,EAAE,UAAkB,EAAE,eAAyB;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAChD,oFAAoF;QACpF,yFAAyF;QACzF,wEAAwE;QACxE,IAAI,YAAY,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACxD,OAAO,YAAY,CAAC,UAAU,CAAC;QACnC,CAAC;QACD,2FAA2F;QAC3F,OAAO,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;IACxG,CAAC;IAED;;;;;OAKG;IACH,kBAAkB,CAAC,UAAkB,EAAE,UAAkB,EAAE,UAAmB;QAC1E,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IACpE,CAAC;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,UAAkB,EAAE,UAAkB;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC;IACnD,CAAC;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,UAAkB,EAAE,UAAkB,EAAE,MAAc;QACxE,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,UAAkB,EAAE,UAAkB;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC/D,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,UAAkB;QAC3B,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,UAAkB;QACnC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,iBAAiB,KAAK,IAAI,CAAC;IACvE,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,UAAkB,EAAE,SAAoC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,QAAQ,GAAc;YACxB,GAAG,YAAY;YACf,SAAS;YACT,iBAAiB,EAAE,IAAI;SAC1B,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,UAAkB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC;IAC5D,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,UAAkB,EAAE,IAAa;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,QAAQ,GAAc;YACxB,GAAG,YAAY;YACf,eAAe,EAAE,IAAI;SACxB,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,UAAkB,EAAE,WAAqB;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,WAAW,GAAG,EAAE,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;QAEjD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC5B,WAAW,CAAC,GAAG,CAAC,GAAG;gBACf,GAAG,qBAAqB;gBACxB,GAAG,WAAW,CAAC,GAAG,CAAC;gBACnB,UAAU,EAAE,IAAI;aACnB,CAAC;QACN,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,UAAkB,EAAE,WAAqB;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,WAAW,GAAG,EAAE,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;QAEjD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC5B,WAAW,CAAC,GAAG,CAAC,GAAG;gBACf,GAAG,qBAAqB;gBACxB,GAAG,WAAW,CAAC,GAAG,CAAC;gBACnB,UAAU,EAAE,KAAK;aACpB,CAAC;QACN,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,UAAkB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,kBAAkB,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,UAAkB;QAC9B,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,UAAkB,EAAE,YAAsB;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,QAAQ,GAAc;YACxB,GAAG,YAAY;YACf,YAAY;SACf,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,UAAkB;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,MAAM,EACF,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,KAAK,EACtB,qBAAqB,EAAE,MAAM,EAC7B,GAAG,kBAAkB,EACxB,GAAG,YAAY,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,kBAA+B,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED,kBAAkB,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC;IAC5D,CAAC;IAED,kBAAkB,CAAC,UAAkB,EAAE,eAAyB;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED,wBAAwB,CAAC,UAAkB;QACvC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,qBAAqB,CAAC;IAClE,CAAC;IAED,wBAAwB,CAAC,UAAkB,EAAE,qBAA+B;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED,mBAAmB,CACf,UAAkB,EAClB,eAAyB,EACzB,qBAA+B;QAE/B,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,qBAAqB,CAAC,UAAkB;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,UAAkB,EAAE,OAAgB;QAC5C,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACxC,uCAAuC;YACvC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,UAAkB,EAAE,WAAqB;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,OAAO,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACpC,4EAA4E;YAC5E,6EAA6E;YAC7E,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,UAAU,CAAC;QACtF,CAAC,CAAC,CAAC,MAAM,CAAC;IACd,CAAC;IAED,4DAA4D;IAE5D;;OAEG;IACK,kBAAkB,CAAC,UAAkB;QACzC,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,GAAG,IAAI,eAAe,CAAY,EAAE,GAAG,kBAAkB,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,UAAkB,EAAE,UAAkB,EAAE,OAAkC;QACjG,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QACnC,wFAAwF;QACxF,kFAAkF;QAClF,gEAAgE;QAChE,MAAM,cAAc,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAE/D,MAAM,QAAQ,GAAc;YACxB,GAAG,YAAY;YACf,QAAQ,EAAE;gBACN,GAAG,YAAY,CAAC,QAAQ;gBACxB,CAAC,UAAU,CAAC,EAAE;oBACV,GAAG,cAAc;oBACjB,GAAG,OAAO;iBACb;aACJ;SACJ,CAAC;QAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,UAAkB;QACpC,OAAO,GAAG,kBAAkB,GAAG,UAAU,EAAE,CAAC;IAChD,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,SAAS,CAAC,UAAkB;QACtC,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,OAAO;YACX,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC;YAEvC,yCAAyC;YACzC,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC;YAExE,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;YAEpD,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;gBACjB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAuB,CAAC;gBACnE,oEAAoE;gBACpE,gEAAgE;gBAChE,0DAA0D;gBAC1D,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,kBAAkB,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACJ,+BAA+B;gBAC/B,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,kBAAkB,EAAE,CAAC,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,iCAAiC,UAAU,GAAG,EAAE,KAAK,CAAC,CAAC;YACpE,8BAA8B;QAClC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,SAAS,CAAC,UAAkB;QAChC,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,OAAO;QACX,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO;QACX,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAC/C,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnF,CAAC;0GAteQ,gBAAgB;gEAAhB,gBAAgB,WAAhB,gBAAgB,mBAFb,MAAM;;iFAET,gBAAgB;cAH5B,UAAU;eAAC;gBACR,UAAU,EAAE,MAAM;aACrB","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { Metadata, IMetadataProvider } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { FormState, FormSectionState, DEFAULT_FORM_STATE, DEFAULT_SECTION_STATE } from './form-state.interface';\n\nconst SETTING_KEY_PREFIX = 'Form.State.';\n\n/**\n * Service for managing form state persistence per entity.\n * State is stored in the User Settings entity and shared reactively\n * across all form instances for the same entity.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class FormStateService {\n /** Cache of BehaviorSubjects per entity name */\n private stateCache = new Map<string, BehaviorSubject<FormState>>();\n\n /** Track which entities have been loaded from DB */\n private loadedEntities = new Set<string>();\n\n /** Track loading promises to prevent duplicate loads */\n private loadingPromises = new Map<string, Promise<void>>();\n\n /** Track which entities are currently in edit mode (saves suppressed) */\n private editingEntities = new Set<string>();\n\n private _provider: IMetadataProvider | null = null;\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n private get metadata(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n /**\n * Get the observable state for an entity.\n * Automatically loads from User Settings on first access.\n * @param entityName The entity name\n * @returns Observable of the form state\n */\n getState$(entityName: string): Observable<FormState> {\n return this.getOrCreateSubject(entityName).asObservable();\n }\n\n /**\n * Get the current state value for an entity.\n * @param entityName The entity name\n * @returns Current form state\n */\n getCurrentState(entityName: string): FormState {\n return this.getOrCreateSubject(entityName).value;\n }\n\n /**\n * Initialize state for an entity by loading from User Settings.\n * Call this when a form component initializes.\n * @param entityName The entity name\n */\n async initializeState(entityName: string): Promise<FormState> {\n // If already loaded, return current state\n if (this.loadedEntities.has(entityName)) {\n return this.getCurrentState(entityName);\n }\n\n // If currently loading, wait for that promise\n const existingPromise = this.loadingPromises.get(entityName);\n if (existingPromise) {\n await existingPromise;\n return this.getCurrentState(entityName);\n }\n\n // Start loading\n const loadPromise = this.loadState(entityName);\n this.loadingPromises.set(entityName, loadPromise);\n\n try {\n await loadPromise;\n this.loadedEntities.add(entityName);\n return this.getCurrentState(entityName);\n } finally {\n this.loadingPromises.delete(entityName);\n }\n }\n\n /**\n * Get section state, returning defaults if section doesn't exist yet.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @returns Section state with defaults applied\n */\n getSectionState(entityName: string, sectionKey: string): FormSectionState {\n const state = this.getCurrentState(entityName);\n return state.sections[sectionKey] || { ...DEFAULT_SECTION_STATE };\n }\n\n /**\n * Check if a section is expanded.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param defaultExpanded Optional default value to use when no persisted state exists (defaults to DEFAULT_SECTION_STATE.isExpanded)\n * @returns True if expanded\n */\n isSectionExpanded(entityName: string, sectionKey: string, defaultExpanded?: boolean): boolean {\n const state = this.getCurrentState(entityName);\n const sectionState = state.sections[sectionKey];\n // Only honor a persisted expansion when it was EXPLICITLY set. A section entry that\n // exists solely to hold a panelHeight has isExpanded === undefined and must fall through\n // to the caller's seeded default (collapsed for related-entity panels).\n if (sectionState && sectionState.isExpanded !== undefined) {\n return sectionState.isExpanded;\n }\n // No explicit persisted state - use provided default or fall back to DEFAULT_SECTION_STATE\n return defaultExpanded !== undefined ? defaultExpanded : (DEFAULT_SECTION_STATE.isExpanded ?? true);\n }\n\n /**\n * Set section expanded state.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param isExpanded Whether the section is expanded\n */\n setSectionExpanded(entityName: string, sectionKey: string, isExpanded: boolean): void {\n this.updateSectionState(entityName, sectionKey, { isExpanded });\n }\n\n /**\n * Get the persisted panel height for a section.\n * @param entityName The entity name\n * @param sectionKey The section key\n * @returns Panel height in pixels, or undefined if no custom height is set\n */\n getSectionPanelHeight(entityName: string, sectionKey: string): number | undefined {\n const state = this.getCurrentState(entityName);\n return state.sections[sectionKey]?.panelHeight;\n }\n\n /**\n * Set the panel height for a section (persisted to User Settings).\n * @param entityName The entity name\n * @param sectionKey The section key\n * @param height Panel height in pixels\n */\n setSectionPanelHeight(entityName: string, sectionKey: string, height: number): void {\n this.updateSectionState(entityName, sectionKey, { panelHeight: height });\n }\n\n /**\n * Toggle section expanded state.\n * @param entityName The entity name\n * @param sectionKey The section key\n */\n toggleSection(entityName: string, sectionKey: string): void {\n const current = this.isSectionExpanded(entityName, sectionKey);\n this.setSectionExpanded(entityName, sectionKey, !current);\n }\n\n /**\n * Get form width mode.\n * @param entityName The entity name\n * @returns Width mode ('centered' or 'full-width')\n */\n getWidthMode(entityName: string): 'centered' | 'full-width' {\n return this.getCurrentState(entityName).widthMode;\n }\n\n /**\n * Whether this entity has had an EXPLICIT widthMode set via the toolbar\n * width-toggle (and thus the user's choice should win over component\n * defaults). Returns false for pre-existing persisted blobs that had a\n * default `widthMode` serialized as a side effect of other state saves.\n */\n hasExplicitWidthMode(entityName: string): boolean {\n return this.getCurrentState(entityName).widthModeExplicit === true;\n }\n\n /**\n * Set form width mode. Marks the preference as explicit so it wins over\n * any component-level default in `BaseFormComponent.getFormWidthMode`.\n */\n setWidthMode(entityName: string, widthMode: 'centered' | 'full-width'): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n widthMode,\n widthModeExplicit: true\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Toggle form width mode between centered and full-width.\n * @param entityName The entity name\n */\n toggleWidthMode(entityName: string): void {\n const current = this.getWidthMode(entityName);\n this.setWidthMode(entityName, current === 'centered' ? 'full-width' : 'centered');\n }\n\n /**\n * Get showEmptyFields preference for an entity.\n * @param entityName The entity name\n * @returns Whether to show empty fields\n */\n getShowEmptyFields(entityName: string): boolean {\n return this.getCurrentState(entityName).showEmptyFields;\n }\n\n /**\n * Set showEmptyFields preference for an entity.\n * @param entityName The entity name\n * @param show Whether to show empty fields\n */\n setShowEmptyFields(entityName: string, show: boolean): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n showEmptyFields: show\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Expand all sections for an entity.\n * @param entityName The entity name\n * @param sectionKeys Array of all section keys to expand\n */\n expandAllSections(entityName: string, sectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newSections = { ...currentState.sections };\n\n for (const key of sectionKeys) {\n newSections[key] = {\n ...DEFAULT_SECTION_STATE,\n ...newSections[key],\n isExpanded: true\n };\n }\n\n subject.next({ ...currentState, sections: newSections });\n this.queueSave(entityName);\n }\n\n /**\n * Collapse all sections for an entity.\n * @param entityName The entity name\n * @param sectionKeys Array of all section keys to collapse\n */\n collapseAllSections(entityName: string, sectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newSections = { ...currentState.sections };\n\n for (const key of sectionKeys) {\n newSections[key] = {\n ...DEFAULT_SECTION_STATE,\n ...newSections[key],\n isExpanded: false\n };\n }\n\n subject.next({ ...currentState, sections: newSections });\n this.queueSave(entityName);\n }\n\n /**\n * Reset state to defaults for an entity.\n * @param entityName The entity name\n */\n resetToDefaults(entityName: string): void {\n const subject = this.getOrCreateSubject(entityName);\n subject.next({ ...DEFAULT_FORM_STATE });\n this.queueSave(entityName);\n }\n\n /**\n * Get the custom section order for an entity.\n * @param entityName The entity name\n * @returns Array of section keys in user's preferred order, or undefined if using default order\n */\n getSectionOrder(entityName: string): string[] | undefined {\n return this.getCurrentState(entityName).sectionOrder;\n }\n\n /**\n * Set the custom section order for an entity.\n * @param entityName The entity name\n * @param sectionOrder Array of section keys in the desired order\n */\n setSectionOrder(entityName: string, sectionOrder: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const newState: FormState = {\n ...currentState,\n sectionOrder\n };\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Reset the section order to default (removes custom ordering).\n * @param entityName The entity name\n */\n resetSectionOrder(entityName: string): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n const {\n sectionOrder: _order,\n moreSectionKeys: _more,\n firstClassSectionKeys: _first,\n ...stateWithoutChrome\n } = currentState;\n subject.next(stateWithoutChrome as FormState);\n this.queueSave(entityName);\n }\n\n getMoreSectionKeys(entityName: string): string[] | undefined {\n return this.getCurrentState(entityName).moreSectionKeys;\n }\n\n setMoreSectionKeys(entityName: string, moreSectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n subject.next({ ...subject.value, moreSectionKeys });\n this.queueSave(entityName);\n }\n\n getFirstClassSectionKeys(entityName: string): string[] | undefined {\n return this.getCurrentState(entityName).firstClassSectionKeys;\n }\n\n setFirstClassSectionKeys(entityName: string, firstClassSectionKeys: string[]): void {\n const subject = this.getOrCreateSubject(entityName);\n subject.next({ ...subject.value, firstClassSectionKeys });\n this.queueSave(entityName);\n }\n\n setChromeMembership(\n entityName: string,\n moreSectionKeys: string[],\n firstClassSectionKeys: string[],\n ): void {\n const subject = this.getOrCreateSubject(entityName);\n subject.next({ ...subject.value, moreSectionKeys, firstClassSectionKeys });\n this.queueSave(entityName);\n }\n\n /**\n * Check if a custom section order exists for an entity.\n * @param entityName The entity name\n * @returns True if custom order exists\n */\n hasCustomSectionOrder(entityName: string): boolean {\n const order = this.getSectionOrder(entityName);\n return order !== undefined && order.length > 0;\n }\n\n /**\n * Set edit mode for an entity. While in edit mode, state changes update\n * the in-memory BehaviorSubject (so the UI stays reactive) but skip\n * database persistence. When edit mode ends, a single save is queued\n * to persist the final layout state.\n * @param entityName The entity name\n * @param editing Whether the form is entering (true) or exiting (false) edit mode\n */\n setEditMode(entityName: string, editing: boolean): void {\n if (editing) {\n this.editingEntities.add(entityName);\n } else {\n this.editingEntities.delete(entityName);\n // Persist the final state once on exit\n this.queueSave(entityName);\n }\n }\n\n /**\n * Get the count of expanded sections.\n * @param entityName The entity name\n * @param sectionKeys Array of section keys to check\n * @returns Number of expanded sections\n */\n getExpandedCount(entityName: string, sectionKeys: string[]): number {\n const state = this.getCurrentState(entityName);\n return sectionKeys.filter(key => {\n const section = state.sections[key];\n // No entry → seeded global default; entry with explicit value → that value;\n // entry with only a panelHeight (isExpanded undefined) → treat as collapsed.\n return section ? (section.isExpanded ?? false) : DEFAULT_SECTION_STATE.isExpanded;\n }).length;\n }\n\n // -------------------- Private Methods --------------------\n\n /**\n * Get or create the BehaviorSubject for an entity.\n */\n private getOrCreateSubject(entityName: string): BehaviorSubject<FormState> {\n let subject = this.stateCache.get(entityName);\n if (!subject) {\n subject = new BehaviorSubject<FormState>({ ...DEFAULT_FORM_STATE });\n this.stateCache.set(entityName, subject);\n }\n return subject;\n }\n\n /**\n * Update a single section's state.\n */\n private updateSectionState(entityName: string, sectionKey: string, updates: Partial<FormSectionState>): void {\n const subject = this.getOrCreateSubject(entityName);\n const currentState = subject.value;\n // Start from the existing entry or an EMPTY object — do NOT seed DEFAULT_SECTION_STATE.\n // Seeding it would force isExpanded:true onto a section that's only being given a\n // panelHeight, silently expanding panels the user never opened.\n const currentSection = currentState.sections[sectionKey] || {};\n\n const newState: FormState = {\n ...currentState,\n sections: {\n ...currentState.sections,\n [sectionKey]: {\n ...currentSection,\n ...updates\n }\n }\n };\n\n subject.next(newState);\n this.queueSave(entityName);\n }\n\n /**\n * Generate the User Settings key for an entity.\n */\n private getSettingKey(entityName: string): string {\n return `${SETTING_KEY_PREFIX}${entityName}`;\n }\n\n /**\n * Load state from User Settings using UserInfoEngine for cached access.\n */\n private async loadState(entityName: string): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n return;\n }\n\n const settingKey = this.getSettingKey(entityName);\n const engine = UserInfoEngine.Instance;\n\n // Find setting from cached user settings\n const setting = engine.UserSettings.find(s => s.Setting === settingKey);\n\n const subject = this.getOrCreateSubject(entityName);\n\n if (setting?.Value) {\n const savedState = JSON.parse(setting.Value) as Partial<FormState>;\n // Merge with defaults to handle new properties. `widthModeExplicit`\n // rides along with the blob — pre-existing blobs won't have it,\n // which is the desired behavior (component defaults win).\n subject.next({ ...DEFAULT_FORM_STATE, ...savedState });\n } else {\n // No saved state, use defaults\n subject.next({ ...DEFAULT_FORM_STATE });\n }\n } catch (error) {\n console.warn(`Failed to load form state for ${entityName}:`, error);\n // Keep default state on error\n }\n }\n\n /**\n * Queue a debounced save using UserInfoEngine's centralized debounce.\n * Skipped when the entity is in edit mode to avoid persisting transient\n * layout changes made while editing a record.\n */\n private queueSave(entityName: string): void {\n if (this.editingEntities.has(entityName)) {\n return;\n }\n\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n return;\n }\n\n const settingKey = this.getSettingKey(entityName);\n const state = this.getCurrentState(entityName);\n UserInfoEngine.Instance.SetSettingDebounced(settingKey, JSON.stringify(state));\n }\n}\n"]}