@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.
- package/LICENSE +180 -4
- package/README.md +4 -1
- package/dist/lib/base-form-component.d.ts +93 -3
- package/dist/lib/base-form-component.d.ts.map +1 -1
- package/dist/lib/base-form-component.js +240 -9
- package/dist/lib/base-form-component.js.map +1 -1
- package/dist/lib/chrome/base-form-policy.d.ts +26 -0
- package/dist/lib/chrome/base-form-policy.d.ts.map +1 -0
- package/dist/lib/chrome/base-form-policy.js +9 -0
- package/dist/lib/chrome/base-form-policy.js.map +1 -0
- package/dist/lib/chrome/clipped-title.d.ts +8 -0
- package/dist/lib/chrome/clipped-title.d.ts.map +1 -0
- package/dist/lib/chrome/clipped-title.js +18 -0
- package/dist/lib/chrome/clipped-title.js.map +1 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.d.ts +35 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.js +96 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.js.map +1 -0
- package/dist/lib/chrome/form-chrome-rail-pref.d.ts +24 -0
- package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome-rail-pref.js +52 -0
- package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -0
- package/dist/lib/chrome/form-chrome.d.ts +59 -0
- package/dist/lib/chrome/form-chrome.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome.js +37 -0
- package/dist/lib/chrome/form-chrome.js.map +1 -0
- package/dist/lib/chrome/load-form-chrome-rules.d.ts +8 -0
- package/dist/lib/chrome/load-form-chrome-rules.d.ts.map +1 -0
- package/dist/lib/chrome/load-form-chrome-rules.js +70 -0
- package/dist/lib/chrome/load-form-chrome-rules.js.map +1 -0
- package/dist/lib/chrome/resolve-form-chrome.d.ts +114 -0
- package/dist/lib/chrome/resolve-form-chrome.d.ts.map +1 -0
- package/dist/lib/chrome/resolve-form-chrome.js +713 -0
- package/dist/lib/chrome/resolve-form-chrome.js.map +1 -0
- package/dist/lib/container/record-form-container.component.d.ts +170 -6
- package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
- package/dist/lib/container/record-form-container.component.js +1194 -62
- package/dist/lib/container/record-form-container.component.js.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.d.ts +54 -1
- package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.js +143 -7
- package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
- package/dist/lib/form-record-refresh.coordinator.d.ts +27 -0
- package/dist/lib/form-record-refresh.coordinator.d.ts.map +1 -0
- package/dist/lib/form-record-refresh.coordinator.js +33 -0
- package/dist/lib/form-record-refresh.coordinator.js.map +1 -0
- package/dist/lib/form-state.interface.d.ts +4 -0
- package/dist/lib/form-state.interface.d.ts.map +1 -1
- package/dist/lib/form-state.interface.js.map +1 -1
- package/dist/lib/form-state.service.d.ts +5 -0
- package/dist/lib/form-state.service.d.ts.map +1 -1
- package/dist/lib/form-state.service.js +23 -2
- package/dist/lib/form-state.service.js.map +1 -1
- package/dist/lib/host/entity-form-host.component.d.ts +5 -1
- package/dist/lib/host/entity-form-host.component.d.ts.map +1 -1
- package/dist/lib/host/entity-form-host.component.js +16 -2
- package/dist/lib/host/entity-form-host.component.js.map +1 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts +6 -2
- package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts.map +1 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.js +15 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.js.map +1 -1
- package/dist/lib/panel/collapsible-panel.component.d.ts +24 -2
- package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
- package/dist/lib/panel/collapsible-panel.component.js +104 -10
- package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.d.ts +85 -1
- package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.js +72 -3
- package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
- package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts +8 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts.map +1 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.js +19 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.js.map +1 -0
- package/dist/lib/panel-slot/form-contribution.d.ts +79 -0
- package/dist/lib/panel-slot/form-contribution.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-contribution.js +253 -0
- package/dist/lib/panel-slot/form-contribution.js.map +1 -0
- package/dist/lib/panel-slot/form-contributions.component.d.ts +27 -0
- package/dist/lib/panel-slot/form-contributions.component.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-contributions.component.js +83 -0
- package/dist/lib/panel-slot/form-contributions.component.js.map +1 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts +2 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -1
- package/dist/lib/panel-slot/form-panel-slot.component.js +35 -4
- package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -1
- package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts +22 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts.map +1 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.js +78 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.js.map +1 -0
- package/dist/lib/related-grid-height.d.ts +20 -0
- package/dist/lib/related-grid-height.d.ts.map +1 -0
- package/dist/lib/related-grid-height.js +30 -0
- package/dist/lib/related-grid-height.js.map +1 -0
- package/dist/lib/section-manager/section-manager.component.d.ts +20 -1
- package/dist/lib/section-manager/section-manager.component.d.ts.map +1 -1
- package/dist/lib/section-manager/section-manager.component.js +204 -25
- package/dist/lib/section-manager/section-manager.component.js.map +1 -1
- package/dist/lib/toolbar/form-toolbar.component.d.ts +55 -4
- package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
- package/dist/lib/toolbar/form-toolbar.component.js +734 -306
- package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
- package/dist/lib/types/form-events.d.ts +35 -0
- package/dist/lib/types/form-events.d.ts.map +1 -1
- package/dist/lib/types/form-events.js +45 -0
- package/dist/lib/types/form-events.js.map +1 -1
- package/dist/lib/types/form-toolbar-item.d.ts +145 -0
- package/dist/lib/types/form-toolbar-item.d.ts.map +1 -0
- package/dist/lib/types/form-toolbar-item.js +2 -0
- package/dist/lib/types/form-toolbar-item.js.map +1 -0
- package/dist/lib/types/form-types.d.ts +8 -0
- package/dist/lib/types/form-types.d.ts.map +1 -1
- package/dist/lib/types/form-types.js.map +1 -1
- package/dist/lib/types/toolbar-config.d.ts +7 -2
- package/dist/lib/types/toolbar-config.d.ts.map +1 -1
- package/dist/lib/types/toolbar-config.js +6 -0
- package/dist/lib/types/toolbar-config.js.map +1 -1
- package/dist/module.d.ts +17 -14
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +14 -0
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +13 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +14 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +19 -18
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { CollectFormPanelRegistrations } from './collect-form-panel-registrations';
|
|
3
|
+
import { ResolveFormContributions, } from './form-contribution';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
import * as i1 from "./related-entity-grid-panel.component";
|
|
6
|
+
const _forTrack0 = ($index, $item) => $item.ContributionKey;
|
|
7
|
+
function FormContributionsComponent_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
8
|
+
i0.ɵɵelement(0, "mj-related-entity-grid-panel", 0);
|
|
9
|
+
} if (rf & 2) {
|
|
10
|
+
const grid_r1 = ctx.$implicit;
|
|
11
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
12
|
+
i0.ɵɵproperty("Contribution", grid_r1)("Record", ctx_r1.Record)("FormComponent", ctx_r1.FormComponent)("FormContext", ctx_r1.FormContext);
|
|
13
|
+
} }
|
|
14
|
+
/**
|
|
15
|
+
* Fills in related-entity grids that the form template did not bake and that
|
|
16
|
+
* no registered panel claimed. Lives in `<mj-record-form-container>` so every
|
|
17
|
+
* generated and custom form that uses the container picks it up.
|
|
18
|
+
*
|
|
19
|
+
* Claimed / extra panels still mount via `<mj-form-panel-slot>` — this host
|
|
20
|
+
* does not remount those (avoids doubles).
|
|
21
|
+
*/
|
|
22
|
+
export class FormContributionsComponent {
|
|
23
|
+
Record;
|
|
24
|
+
FormComponent;
|
|
25
|
+
FormContext;
|
|
26
|
+
BakedSectionKeys = [];
|
|
27
|
+
ShowRelatedEntities = true;
|
|
28
|
+
StockGrids = [];
|
|
29
|
+
ngOnChanges(changes) {
|
|
30
|
+
const keys = ['Record', 'FormComponent', 'BakedSectionKeys', 'ShowRelatedEntities'];
|
|
31
|
+
const meaningful = keys.some((k) => changes[k] && changes[k].currentValue !== changes[k].previousValue);
|
|
32
|
+
if (!meaningful && this.StockGrids.length > 0)
|
|
33
|
+
return;
|
|
34
|
+
this.refresh();
|
|
35
|
+
}
|
|
36
|
+
refresh() {
|
|
37
|
+
if (!this.Record?.EntityInfo || !this.FormComponent) {
|
|
38
|
+
this.StockGrids = [];
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const entity = this.Record.EntityInfo;
|
|
42
|
+
const resolved = ResolveFormContributions({
|
|
43
|
+
EntityName: entity.Name,
|
|
44
|
+
RelatedEntities: entity.RelatedEntities,
|
|
45
|
+
IsaChildEntityIDs: entity.ChildEntities.map((child) => child.ID),
|
|
46
|
+
Registrations: CollectFormPanelRegistrations(),
|
|
47
|
+
BakedSectionKeys: this.BakedSectionKeys,
|
|
48
|
+
ShowRelatedEntities: this.ShowRelatedEntities,
|
|
49
|
+
});
|
|
50
|
+
this.StockGrids = resolved.StockGrids;
|
|
51
|
+
}
|
|
52
|
+
static ɵfac = function FormContributionsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormContributionsComponent)(); };
|
|
53
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FormContributionsComponent, selectors: [["mj-form-contributions"]], inputs: { Record: "Record", FormComponent: "FormComponent", FormContext: "FormContext", BakedSectionKeys: "BakedSectionKeys", ShowRelatedEntities: "ShowRelatedEntities" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 2, vars: 0, consts: [[3, "Contribution", "Record", "FormComponent", "FormContext"]], template: function FormContributionsComponent_Template(rf, ctx) { if (rf & 1) {
|
|
54
|
+
i0.ɵɵrepeaterCreate(0, FormContributionsComponent_For_1_Template, 1, 4, "mj-related-entity-grid-panel", 0, _forTrack0);
|
|
55
|
+
} if (rf & 2) {
|
|
56
|
+
i0.ɵɵrepeater(ctx.StockGrids);
|
|
57
|
+
} }, dependencies: [i1.RelatedEntityGridPanelComponent], styles: ["[_nghost-%COMP%] { display: contents; }"] });
|
|
58
|
+
}
|
|
59
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormContributionsComponent, [{
|
|
60
|
+
type: Component,
|
|
61
|
+
args: [{ standalone: false, selector: 'mj-form-contributions', template: `
|
|
62
|
+
@for (grid of StockGrids; track grid.ContributionKey) {
|
|
63
|
+
<mj-related-entity-grid-panel
|
|
64
|
+
[Contribution]="grid"
|
|
65
|
+
[Record]="Record"
|
|
66
|
+
[FormComponent]="FormComponent"
|
|
67
|
+
[FormContext]="FormContext">
|
|
68
|
+
</mj-related-entity-grid-panel>
|
|
69
|
+
}
|
|
70
|
+
`, styles: [":host { display: contents; }"] }]
|
|
71
|
+
}], null, { Record: [{
|
|
72
|
+
type: Input
|
|
73
|
+
}], FormComponent: [{
|
|
74
|
+
type: Input
|
|
75
|
+
}], FormContext: [{
|
|
76
|
+
type: Input
|
|
77
|
+
}], BakedSectionKeys: [{
|
|
78
|
+
type: Input
|
|
79
|
+
}], ShowRelatedEntities: [{
|
|
80
|
+
type: Input
|
|
81
|
+
}] }); })();
|
|
82
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormContributionsComponent, { className: "FormContributionsComponent", filePath: "src/lib/panel-slot/form-contributions.component.ts", lineNumber: 34 }); })();
|
|
83
|
+
//# sourceMappingURL=form-contributions.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-contributions.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-contributions.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAA4B,MAAM,eAAe,CAAC;AAI3E,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EACH,wBAAwB,GAE3B,MAAM,qBAAqB,CAAC;;;;;IAejB,kDAK+B;;;;IAD3B,AADA,AADA,AADA,sCAAqB,yBACJ,uCACc,mCACJ;;AAjB3C;;;;;;;GAOG;AAgBH,MAAM,OAAO,0BAA0B;IAC1B,MAAM,CAAc;IACpB,aAAa,CAAqB;IAClC,WAAW,CAAe;IAC1B,gBAAgB,GAAa,EAAE,CAAC;IAChC,mBAAmB,GAAG,IAAI,CAAC;IAE7B,UAAU,GAA6B,EAAE,CAAC;IAE1C,WAAW,CAAC,OAAsB;QACrC,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,CAAC,CAAC;QACpF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QACxG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAEO,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAClD,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;YACrB,OAAO;QACX,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;QACtC,MAAM,QAAQ,GAAG,wBAAwB,CAAC;YACtC,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,iBAAiB,EAAE,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,aAAa,EAAE,6BAA6B,EAAE;YAC9C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;SAChD,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IAC1C,CAAC;oHA/BQ,0BAA0B;6DAA1B,0BAA0B;YAX/B,sHAOC;;YAPD,6BAOC;;;iFAII,0BAA0B;cAftC,SAAS;6BACM,KAAK,YACP,uBAAuB,YACvB;;;;;;;;;KAST;;kBAIA,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kFALG,0BAA0B","sourcesContent":["import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';\nimport { BaseEntity } from '@memberjunction/core';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { CollectFormPanelRegistrations } from './collect-form-panel-registrations';\nimport {\n ResolveFormContributions,\n type FormContributionWinner,\n} from './form-contribution';\n\n/**\n * Fills in related-entity grids that the form template did not bake and that\n * no registered panel claimed. Lives in `<mj-record-form-container>` so every\n * generated and custom form that uses the container picks it up.\n *\n * Claimed / extra panels still mount via `<mj-form-panel-slot>` — this host\n * does not remount those (avoids doubles).\n */\n@Component({\n standalone: false,\n selector: 'mj-form-contributions',\n template: `\n @for (grid of StockGrids; track grid.ContributionKey) {\n <mj-related-entity-grid-panel\n [Contribution]=\"grid\"\n [Record]=\"Record\"\n [FormComponent]=\"FormComponent\"\n [FormContext]=\"FormContext\">\n </mj-related-entity-grid-panel>\n }\n `,\n styles: [`:host { display: contents; }`],\n})\nexport class FormContributionsComponent implements OnChanges {\n @Input() Record!: BaseEntity;\n @Input() FormComponent!: BaseFormComponent;\n @Input() FormContext?: FormContext;\n @Input() BakedSectionKeys: string[] = [];\n @Input() ShowRelatedEntities = true;\n\n public StockGrids: FormContributionWinner[] = [];\n\n public ngOnChanges(changes: SimpleChanges): void {\n const keys = ['Record', 'FormComponent', 'BakedSectionKeys', 'ShowRelatedEntities'];\n const meaningful = keys.some((k) => changes[k] && changes[k].currentValue !== changes[k].previousValue);\n if (!meaningful && this.StockGrids.length > 0) return;\n this.refresh();\n }\n\n private refresh(): void {\n if (!this.Record?.EntityInfo || !this.FormComponent) {\n this.StockGrids = [];\n return;\n }\n const entity = this.Record.EntityInfo;\n const resolved = ResolveFormContributions({\n EntityName: entity.Name,\n RelatedEntities: entity.RelatedEntities,\n IsaChildEntityIDs: entity.ChildEntities.map((child) => child.ID),\n Registrations: CollectFormPanelRegistrations(),\n BakedSectionKeys: this.BakedSectionKeys,\n ShowRelatedEntities: this.ShowRelatedEntities,\n });\n this.StockGrids = resolved.StockGrids;\n }\n}\n"]}
|
|
@@ -52,6 +52,7 @@ export declare class FormPanelSlotComponent implements OnInit, OnChanges, OnDest
|
|
|
52
52
|
private mounted;
|
|
53
53
|
private readonly destroy$;
|
|
54
54
|
private registeredSlot;
|
|
55
|
+
private readonly recordRefresh;
|
|
55
56
|
/** Tracks synchronous re-entry depth into remount() so a future refactor
|
|
56
57
|
* that reintroduces a remount loop surfaces loudly instead of freezing. */
|
|
57
58
|
private remountDepth;
|
|
@@ -59,6 +60,7 @@ export declare class FormPanelSlotComponent implements OnInit, OnChanges, OnDest
|
|
|
59
60
|
ngOnInit(): void;
|
|
60
61
|
ngOnChanges(changes: SimpleChanges): void;
|
|
61
62
|
ngOnDestroy(): void;
|
|
63
|
+
private notifyMountedPanels;
|
|
62
64
|
private remount;
|
|
63
65
|
/**
|
|
64
66
|
* Find registrations whose metadata declares this exact entity (or the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-panel-slot.component.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,SAAS,EACT,SAAS,EACT,MAAM,EAEN,aAAa,
|
|
1
|
+
{"version":3,"file":"form-panel-slot.component.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,SAAS,EACT,SAAS,EACT,MAAM,EAEN,aAAa,EAKhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAY,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAgD,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;;AAItE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAWa,sBAAuB,YAAW,MAAM,EAAE,SAAS,EAAE,SAAS;IAuB/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAtBrD,oEAAoE;IAC3D,MAAM,EAAG,MAAM,CAAC;IACzB,gDAAgD;IACvC,IAAI,EAAG,aAAa,CAAC;IAC9B,yEAAyE;IAChE,MAAM,EAAG,UAAU,CAAC;IAC7B,mFAAmF;IAC1E,aAAa,EAAG,iBAAiB,CAAC;IAC3C,2EAA2E;IAClE,WAAW,CAAC,EAAE,WAAW,CAAC;IAGnC,OAAO,CAAC,MAAM,CAAoB;IAElC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4D;IAC1F;gFAC4E;IAC5E,OAAO,CAAC,YAAY,CAAK;gBAEgB,WAAW,CAAC,EAAE,mBAAmB,YAAA;IAE1E,QAAQ,IAAI,IAAI;IAgBhB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IA4BzC,WAAW,IAAI,IAAI;IAUnB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,OAAO;IAgEf;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAWzB,gGAAgG;IAChG,OAAO,CAAC,aAAa;IAWrB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,UAAU;yCA/MT,sBAAsB;2CAAtB,sBAAsB;CAmNlC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Component, Input, Optional, ViewChild, ViewContainerRef, } from '@angular/core';
|
|
1
|
+
import { Component, Input, Optional, ViewChild, ViewContainerRef, inject, } from '@angular/core';
|
|
2
2
|
import { Subject, takeUntil } from 'rxjs';
|
|
3
3
|
import { LogError } from '@memberjunction/core';
|
|
4
4
|
import { MJGlobal } from '@memberjunction/global';
|
|
5
5
|
import { BaseFormPanel } from './base-form-panel';
|
|
6
|
+
import { CollapseFormPanelRegistrations } from './form-contribution';
|
|
7
|
+
import { FormRecordRefreshCoordinator } from '../form-record-refresh.coordinator';
|
|
6
8
|
import * as i0 from "@angular/core";
|
|
7
9
|
import * as i1 from "./form-slot-coordinator.service";
|
|
8
10
|
const _c0 = ["anchor"];
|
|
@@ -53,6 +55,7 @@ export class FormPanelSlotComponent {
|
|
|
53
55
|
mounted = [];
|
|
54
56
|
destroy$ = new Subject();
|
|
55
57
|
registeredSlot = null;
|
|
58
|
+
recordRefresh = inject(FormRecordRefreshCoordinator, { optional: true });
|
|
56
59
|
/** Tracks synchronous re-entry depth into remount() so a future refactor
|
|
57
60
|
* that reintroduces a remount loop surfaces loudly instead of freezing. */
|
|
58
61
|
remountDepth = 0;
|
|
@@ -70,6 +73,9 @@ export class FormPanelSlotComponent {
|
|
|
70
73
|
.pipe(takeUntil(this.destroy$))
|
|
71
74
|
.subscribe(() => this.remount());
|
|
72
75
|
}
|
|
76
|
+
this.recordRefresh?.Refreshed$.pipe(takeUntil(this.destroy$)).subscribe((record) => {
|
|
77
|
+
this.notifyMountedPanels(record);
|
|
78
|
+
});
|
|
73
79
|
}
|
|
74
80
|
ngOnChanges(changes) {
|
|
75
81
|
if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {
|
|
@@ -106,6 +112,11 @@ export class FormPanelSlotComponent {
|
|
|
106
112
|
}
|
|
107
113
|
this.unmountAll();
|
|
108
114
|
}
|
|
115
|
+
notifyMountedPanels(record) {
|
|
116
|
+
for (const ref of this.mounted) {
|
|
117
|
+
ref.instance.OnRecordRefreshed(record);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
109
120
|
remount() {
|
|
110
121
|
this.remountDepth++;
|
|
111
122
|
if (this.remountDepth > 5) {
|
|
@@ -122,7 +133,11 @@ export class FormPanelSlotComponent {
|
|
|
122
133
|
// (no parent container), no orphan handling — panels just bind
|
|
123
134
|
// to their literal slot.
|
|
124
135
|
const orphans = this.findOrphans();
|
|
125
|
-
const all = [...direct, ...orphans]
|
|
136
|
+
const all = CollapseFormPanelRegistrations([...direct, ...orphans].map((reg) => ({
|
|
137
|
+
Priority: reg.Priority,
|
|
138
|
+
Metadata: (reg.Metadata ?? { entity: this.Entity, slot: this.Slot }),
|
|
139
|
+
Registration: reg,
|
|
140
|
+
}))).map((row) => row.Registration);
|
|
126
141
|
if (all.length === 0) {
|
|
127
142
|
this.remountDepth--;
|
|
128
143
|
return;
|
|
@@ -143,6 +158,12 @@ export class FormPanelSlotComponent {
|
|
|
143
158
|
ref.instance.FormComponent = this.FormComponent;
|
|
144
159
|
if (this.FormContext)
|
|
145
160
|
ref.instance.FormContext = this.FormContext;
|
|
161
|
+
// Left-nav leftover height targets mj-collapsible-panel as a
|
|
162
|
+
// flex child of .mj-forms-all-panels. The slot is already
|
|
163
|
+
// display:contents; the mounted host must be too.
|
|
164
|
+
const host = ref.location.nativeElement;
|
|
165
|
+
if (host)
|
|
166
|
+
host.style.display = 'contents';
|
|
146
167
|
// No detectChanges() — Angular's normal CD pass picks the new
|
|
147
168
|
// component up. Calling detectChanges() synchronously inside
|
|
148
169
|
// an ongoing CD cycle (which is when ngOnChanges → remount
|
|
@@ -173,7 +194,17 @@ export class FormPanelSlotComponent {
|
|
|
173
194
|
}
|
|
174
195
|
/** True when a registration's `entity` matches this slot's entity, or is the `'*'` wildcard. */
|
|
175
196
|
entityMatches(registeredEntity) {
|
|
176
|
-
|
|
197
|
+
if (!registeredEntity || !this.Entity)
|
|
198
|
+
return false;
|
|
199
|
+
if (registeredEntity === '*')
|
|
200
|
+
return true;
|
|
201
|
+
const a = registeredEntity.trim().toLowerCase();
|
|
202
|
+
const b = this.Entity.trim().toLowerCase();
|
|
203
|
+
if (a === b)
|
|
204
|
+
return true;
|
|
205
|
+
// Normalize schema prefixes like "MJ: " or "MJ_"
|
|
206
|
+
const strip = (s) => s.replace(/^mj[:_\s]+/i, '').replace(/[\s_]+/g, '').toLowerCase();
|
|
207
|
+
return strip(a) === strip(b);
|
|
177
208
|
}
|
|
178
209
|
/**
|
|
179
210
|
* Find panels registered for a different slot that should mount HERE
|
|
@@ -233,5 +264,5 @@ export class FormPanelSlotComponent {
|
|
|
233
264
|
type: ViewChild,
|
|
234
265
|
args: ['anchor', { read: ViewContainerRef, static: true }]
|
|
235
266
|
}] }); })();
|
|
236
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormPanelSlotComponent, { className: "FormPanelSlotComponent", filePath: "src/lib/panel-slot/form-panel-slot.component.ts", lineNumber:
|
|
267
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormPanelSlotComponent, { className: "FormPanelSlotComponent", filePath: "src/lib/panel-slot/form-panel-slot.component.ts", lineNumber: 67 }); })();
|
|
237
268
|
//# sourceMappingURL=form-panel-slot.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-panel-slot.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ComponentRef,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n SimpleChanges,\n Type,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\nimport { BaseEntity, LogError } from '@memberjunction/core';\nimport { ClassRegistration, MJGlobal } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { BaseFormPanel, FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';\nimport { FormSlotCoordinator } from './form-slot-coordinator.service';\n\n/**\n * `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every\n * registered `BaseFormPanel` whose metadata matches the slot's `Entity` +\n * `Slot` inputs. Generated entity-form HTML emits one of these at each\n * well-known slot position so consumers can extend the form WITHOUT replacing\n * it via the custom-form override pattern.\n *\n * **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`\n * to find every panel registered with metadata `{ entity, slot }` matching this\n * slot. Registrations without a metadata bag (or without these required keys)\n * are silently ignored so legacy registrations don't accidentally bind.\n *\n * **Ordering**: registrations are sorted by `metadata.sortKey` descending —\n * higher number renders first within the slot. Use ranges (100/50/10) so future\n * panels can wedge in without renumbering. Ties fall back to the\n * `ClassRegistration.Priority` field (also descending), and finally to\n * registration order.\n *\n * **Fallback chain** (see `FormSlotCoordinator`): each slot host registers\n * with the per-form coordinator and additionally renders panels whose\n * REGISTERED slot is missing in the current form, when this host happens to\n * be the next-existing slot down the chain. The container template guarantees\n * an `after-everything` slot host, so fallback always terminates somewhere.\n * Downstream consumers running an older form template (no CodeGen rerun\n * yet) get every panel rendered at the bottom; consumers on the new template\n * get them in the preferred position.\n *\n * **Re-mount**: any input change OR a coordinator change (another slot\n * registers/deregisters) triggers a re-render so fallback assignments stay\n * correct as slots come and go.\n */\n@Component({\n standalone: false,\n selector: 'mj-form-panel-slot',\n template: `<ng-container #anchor></ng-container>`,\n // `display: contents` makes the host element disappear from the layout\n // tree so the mounted panels (children) participate directly in the\n // parent's flex/grid layout. Without this, the parent's gap/spacing\n // rules treat all mounted panels as a SINGLE child (the slot host),\n // collapsing the spacing between them.\n styles: [`:host { display: contents; }`],\n})\nexport class FormPanelSlotComponent implements OnInit, OnChanges, OnDestroy {\n /** The entity name (e.g., `\"MJ: Content Sources\"`) being edited. */\n @Input() Entity!: string;\n /** Which slot position this host represents. */\n @Input() Slot!: FormPanelSlot;\n /** The record being edited — threaded through to every mounted panel. */\n @Input() Record!: BaseEntity;\n /** The host form component — used by panels for EditMode + dirty notifications. */\n @Input() FormComponent!: BaseFormComponent;\n /** Optional form context — same shape collapsible-panel chrome expects. */\n @Input() FormContext?: FormContext;\n\n @ViewChild('anchor', { read: ViewContainerRef, static: true })\n private anchor!: ViewContainerRef;\n\n private mounted: ComponentRef<BaseFormPanel>[] = [];\n private readonly destroy$ = new Subject<void>();\n private registeredSlot: FormPanelSlot | null = null;\n /** Tracks synchronous re-entry depth into remount() so a future refactor\n * that reintroduces a remount loop surfaces loudly instead of freezing. */\n private remountDepth = 0;\n\n constructor(@Optional() private readonly coordinator?: FormSlotCoordinator) {}\n\n ngOnInit(): void {\n if (this.Slot && this.coordinator) {\n this.coordinator.registerSlot(this.Slot);\n this.registeredSlot = this.Slot;\n // Re-mount whenever the coordinator's known-slot set changes —\n // a panel that was previously orphaned to us might now have a\n // better slot available (or vice versa).\n this.coordinator.changes\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => this.remount());\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {\n // Inputs still being wired — wait for the next pass.\n return;\n }\n\n // Only remount when something that affects WHICH panels to mount has\n // changed. FormContext only flows through to mounted panels — pushing\n // it to the existing panels avoids an unmount/remount cycle.\n //\n // Critical for performance: the host form often reconstructs\n // formContext on every CD pass. Treating that as a structural change\n // would loop (remount → detectChanges → another CD → new FormContext\n // → remount again).\n const meaningfulKeys = ['Entity', 'Slot', 'Record', 'FormComponent'];\n const meaningfulChange = meaningfulKeys.some(k => changes[k]?.currentValue !== changes[k]?.previousValue);\n if (!meaningfulChange && changes['FormContext'] && this.mounted.length > 0) {\n for (const ref of this.mounted) {\n ref.instance.FormContext = this.FormContext;\n }\n return;\n }\n if (!meaningfulChange && this.mounted.length > 0) {\n return;\n }\n this.remount();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n if (this.coordinator && this.registeredSlot) {\n this.coordinator.deregisterSlot(this.registeredSlot);\n this.registeredSlot = null;\n }\n this.unmountAll();\n }\n\n private remount(): void {\n this.remountDepth++;\n if (this.remountDepth > 5) {\n console.error(`[mj-form-panel-slot] LOOP DETECTED — remount depth ${this.remountDepth} on slot=${this.Slot}. Bailing.`);\n this.remountDepth--;\n return;\n }\n this.unmountAll();\n\n // 1. Direct matches: panels registered for THIS slot.\n const direct = this.findRegistrations(this.Slot);\n\n // 2. Orphans: panels registered for some OTHER slot whose preferred\n // slot doesn't exist in this form, AND this slot host is the\n // coordinator-resolved fallback for them. Without a coordinator\n // (no parent container), no orphan handling — panels just bind\n // to their literal slot.\n const orphans = this.findOrphans();\n\n const all = [...direct, ...orphans];\n if (all.length === 0) {\n this.remountDepth--;\n return;\n }\n\n // Sort: higher sortKey first, then higher Priority, then registration order.\n all.sort((a, b) => {\n const aSort = (a.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n const bSort = (b.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n if (aSort !== bSort) return bSort - aSort;\n return b.Priority - a.Priority;\n });\n\n for (const reg of all) {\n try {\n const ctor = reg.SubClass as Type<BaseFormPanel>;\n const ref = this.anchor.createComponent(ctor);\n ref.instance.Record = this.Record;\n ref.instance.FormComponent = this.FormComponent;\n if (this.FormContext) ref.instance.FormContext = this.FormContext;\n // No detectChanges() — Angular's normal CD pass picks the new\n // component up. Calling detectChanges() synchronously inside\n // an ongoing CD cycle (which is when ngOnChanges → remount\n // fires) can re-enter and cause an infinite loop if anything\n // upstream is reconstructing inputs on each CD pass.\n this.mounted.push(ref);\n } catch (e) {\n LogError(`[mj-form-panel-slot] Failed to mount panel for ${this.Entity}:${this.Slot}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n this.remountDepth--;\n }\n\n /**\n * Find registrations whose metadata declares this exact entity (or the\n * `'*'` wildcard) + slot. A panel registered with `entity: '*'` is\n * entity-agnostic — it mounts on EVERY entity's form. Such panels are\n * expected to self-hide (render nothing) when they don't apply to the\n * current record, so the cross-cutting registration stays unobtrusive.\n */\n private findRegistrations(slot: FormPanelSlot): ClassRegistration[] {\n return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot === slot;\n },\n );\n }\n\n /** True when a registration's `entity` matches this slot's entity, or is the `'*'` wildcard. */\n private entityMatches(registeredEntity: string | undefined): boolean {\n return registeredEntity === '*' || registeredEntity === this.Entity;\n }\n\n /**\n * Find panels registered for a different slot that should mount HERE\n * because their preferred slot isn't present and this slot is the next\n * existing one in the fallback chain.\n *\n * Only meaningful when we have a coordinator (i.e., we're inside an\n * `<mj-record-form-container>`).\n */\n private findOrphans(): ClassRegistration[] {\n if (!this.coordinator) return [];\n\n const allForEntity = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot != null && meta.slot !== this.Slot;\n },\n );\n\n return allForEntity.filter(reg => {\n const meta = reg.Metadata as FormPanelRegistrationMetadata | undefined;\n if (!meta?.slot) return false;\n const resolved = this.coordinator!.resolveSlot(meta.slot);\n return resolved === this.Slot;\n });\n }\n\n private unmountAll(): void {\n this.anchor.clear();\n this.mounted = [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"form-panel-slot.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ComponentRef,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n SimpleChanges,\n Type,\n ViewChild,\n ViewContainerRef,\n inject,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\nimport { BaseEntity, LogError } from '@memberjunction/core';\nimport { ClassRegistration, MJGlobal } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { BaseFormPanel, FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';\nimport { FormSlotCoordinator } from './form-slot-coordinator.service';\nimport { CollapseFormPanelRegistrations } from './form-contribution';\nimport { FormRecordRefreshCoordinator } from '../form-record-refresh.coordinator';\n\n/**\n * `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every\n * registered `BaseFormPanel` whose metadata matches the slot's `Entity` +\n * `Slot` inputs. Generated entity-form HTML emits one of these at each\n * well-known slot position so consumers can extend the form WITHOUT replacing\n * it via the custom-form override pattern.\n *\n * **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`\n * to find every panel registered with metadata `{ entity, slot }` matching this\n * slot. Registrations without a metadata bag (or without these required keys)\n * are silently ignored so legacy registrations don't accidentally bind.\n *\n * **Ordering**: registrations are sorted by `metadata.sortKey` descending —\n * higher number renders first within the slot. Use ranges (100/50/10) so future\n * panels can wedge in without renumbering. Ties fall back to the\n * `ClassRegistration.Priority` field (also descending), and finally to\n * registration order.\n *\n * **Fallback chain** (see `FormSlotCoordinator`): each slot host registers\n * with the per-form coordinator and additionally renders panels whose\n * REGISTERED slot is missing in the current form, when this host happens to\n * be the next-existing slot down the chain. The container template guarantees\n * an `after-everything` slot host, so fallback always terminates somewhere.\n * Downstream consumers running an older form template (no CodeGen rerun\n * yet) get every panel rendered at the bottom; consumers on the new template\n * get them in the preferred position.\n *\n * **Re-mount**: any input change OR a coordinator change (another slot\n * registers/deregisters) triggers a re-render so fallback assignments stay\n * correct as slots come and go.\n */\n@Component({\n standalone: false,\n selector: 'mj-form-panel-slot',\n template: `<ng-container #anchor></ng-container>`,\n // `display: contents` makes the host element disappear from the layout\n // tree so the mounted panels (children) participate directly in the\n // parent's flex/grid layout. Without this, the parent's gap/spacing\n // rules treat all mounted panels as a SINGLE child (the slot host),\n // collapsing the spacing between them.\n styles: [`:host { display: contents; }`],\n})\nexport class FormPanelSlotComponent implements OnInit, OnChanges, OnDestroy {\n /** The entity name (e.g., `\"MJ: Content Sources\"`) being edited. */\n @Input() Entity!: string;\n /** Which slot position this host represents. */\n @Input() Slot!: FormPanelSlot;\n /** The record being edited — threaded through to every mounted panel. */\n @Input() Record!: BaseEntity;\n /** The host form component — used by panels for EditMode + dirty notifications. */\n @Input() FormComponent!: BaseFormComponent;\n /** Optional form context — same shape collapsible-panel chrome expects. */\n @Input() FormContext?: FormContext;\n\n @ViewChild('anchor', { read: ViewContainerRef, static: true })\n private anchor!: ViewContainerRef;\n\n private mounted: ComponentRef<BaseFormPanel>[] = [];\n private readonly destroy$ = new Subject<void>();\n private registeredSlot: FormPanelSlot | null = null;\n private readonly recordRefresh = inject(FormRecordRefreshCoordinator, { optional: true });\n /** Tracks synchronous re-entry depth into remount() so a future refactor\n * that reintroduces a remount loop surfaces loudly instead of freezing. */\n private remountDepth = 0;\n\n constructor(@Optional() private readonly coordinator?: FormSlotCoordinator) {}\n\n ngOnInit(): void {\n if (this.Slot && this.coordinator) {\n this.coordinator.registerSlot(this.Slot);\n this.registeredSlot = this.Slot;\n // Re-mount whenever the coordinator's known-slot set changes —\n // a panel that was previously orphaned to us might now have a\n // better slot available (or vice versa).\n this.coordinator.changes\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => this.remount());\n }\n this.recordRefresh?.Refreshed$.pipe(takeUntil(this.destroy$)).subscribe((record) => {\n this.notifyMountedPanels(record);\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {\n // Inputs still being wired — wait for the next pass.\n return;\n }\n\n // Only remount when something that affects WHICH panels to mount has\n // changed. FormContext only flows through to mounted panels — pushing\n // it to the existing panels avoids an unmount/remount cycle.\n //\n // Critical for performance: the host form often reconstructs\n // formContext on every CD pass. Treating that as a structural change\n // would loop (remount → detectChanges → another CD → new FormContext\n // → remount again).\n const meaningfulKeys = ['Entity', 'Slot', 'Record', 'FormComponent'];\n const meaningfulChange = meaningfulKeys.some(k => changes[k]?.currentValue !== changes[k]?.previousValue);\n if (!meaningfulChange && changes['FormContext'] && this.mounted.length > 0) {\n for (const ref of this.mounted) {\n ref.instance.FormContext = this.FormContext;\n }\n return;\n }\n if (!meaningfulChange && this.mounted.length > 0) {\n return;\n }\n this.remount();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n if (this.coordinator && this.registeredSlot) {\n this.coordinator.deregisterSlot(this.registeredSlot);\n this.registeredSlot = null;\n }\n this.unmountAll();\n }\n\n private notifyMountedPanels(record: BaseEntity): void {\n for (const ref of this.mounted) {\n ref.instance.OnRecordRefreshed(record);\n }\n }\n\n private remount(): void {\n this.remountDepth++;\n if (this.remountDepth > 5) {\n console.error(`[mj-form-panel-slot] LOOP DETECTED — remount depth ${this.remountDepth} on slot=${this.Slot}. Bailing.`);\n this.remountDepth--;\n return;\n }\n this.unmountAll();\n\n // 1. Direct matches: panels registered for THIS slot.\n const direct = this.findRegistrations(this.Slot);\n\n // 2. Orphans: panels registered for some OTHER slot whose preferred\n // slot doesn't exist in this form, AND this slot host is the\n // coordinator-resolved fallback for them. Without a coordinator\n // (no parent container), no orphan handling — panels just bind\n // to their literal slot.\n const orphans = this.findOrphans();\n\n const all = CollapseFormPanelRegistrations(\n [...direct, ...orphans].map((reg) => ({\n Priority: reg.Priority,\n Metadata: (reg.Metadata ?? { entity: this.Entity, slot: this.Slot }) as FormPanelRegistrationMetadata,\n Registration: reg,\n })),\n ).map((row) => row.Registration);\n if (all.length === 0) {\n this.remountDepth--;\n return;\n }\n\n // Sort: higher sortKey first, then higher Priority, then registration order.\n all.sort((a, b) => {\n const aSort = (a.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n const bSort = (b.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n if (aSort !== bSort) return bSort - aSort;\n return b.Priority - a.Priority;\n });\n\n for (const reg of all) {\n try {\n const ctor = reg.SubClass as Type<BaseFormPanel>;\n const ref = this.anchor.createComponent(ctor);\n ref.instance.Record = this.Record;\n ref.instance.FormComponent = this.FormComponent;\n if (this.FormContext) ref.instance.FormContext = this.FormContext;\n // Left-nav leftover height targets mj-collapsible-panel as a\n // flex child of .mj-forms-all-panels. The slot is already\n // display:contents; the mounted host must be too.\n const host = ref.location.nativeElement as HTMLElement | null;\n if (host) host.style.display = 'contents';\n // No detectChanges() — Angular's normal CD pass picks the new\n // component up. Calling detectChanges() synchronously inside\n // an ongoing CD cycle (which is when ngOnChanges → remount\n // fires) can re-enter and cause an infinite loop if anything\n // upstream is reconstructing inputs on each CD pass.\n this.mounted.push(ref);\n } catch (e) {\n LogError(`[mj-form-panel-slot] Failed to mount panel for ${this.Entity}:${this.Slot}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n this.remountDepth--;\n }\n\n /**\n * Find registrations whose metadata declares this exact entity (or the\n * `'*'` wildcard) + slot. A panel registered with `entity: '*'` is\n * entity-agnostic — it mounts on EVERY entity's form. Such panels are\n * expected to self-hide (render nothing) when they don't apply to the\n * current record, so the cross-cutting registration stays unobtrusive.\n */\n private findRegistrations(slot: FormPanelSlot): ClassRegistration[] {\n return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot === slot;\n },\n );\n }\n\n /** True when a registration's `entity` matches this slot's entity, or is the `'*'` wildcard. */\n private entityMatches(registeredEntity: string | undefined): boolean {\n if (!registeredEntity || !this.Entity) return false;\n if (registeredEntity === '*') return true;\n const a = registeredEntity.trim().toLowerCase();\n const b = this.Entity.trim().toLowerCase();\n if (a === b) return true;\n // Normalize schema prefixes like \"MJ: \" or \"MJ_\"\n const strip = (s: string) => s.replace(/^mj[:_\\s]+/i, '').replace(/[\\s_]+/g, '').toLowerCase();\n return strip(a) === strip(b);\n }\n\n /**\n * Find panels registered for a different slot that should mount HERE\n * because their preferred slot isn't present and this slot is the next\n * existing one in the fallback chain.\n *\n * Only meaningful when we have a coordinator (i.e., we're inside an\n * `<mj-record-form-container>`).\n */\n private findOrphans(): ClassRegistration[] {\n if (!this.coordinator) return [];\n\n const allForEntity = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot != null && meta.slot !== this.Slot;\n },\n );\n\n return allForEntity.filter(reg => {\n const meta = reg.Metadata as FormPanelRegistrationMetadata | undefined;\n if (!meta?.slot) return false;\n const resolved = this.coordinator!.resolveSlot(meta.slot);\n return resolved === this.Slot;\n });\n }\n\n private unmountAll(): void {\n this.anchor.clear();\n this.mounted = [];\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
2
|
+
import type { AfterDataLoadEventArgs } from '@memberjunction/ng-entity-viewer';
|
|
3
|
+
import { BaseFormComponent } from '../base-form-component';
|
|
4
|
+
import { FormContext } from '../types/form-types';
|
|
5
|
+
import type { FormContributionWinner } from './form-contribution';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
/**
|
|
8
|
+
* Stock related-entity grid the composer mounts when a DisplayInForm
|
|
9
|
+
* relationship is neither baked into the form template nor claimed by a
|
|
10
|
+
* registered panel. Mirrors the CodeGen EntityDataGrid template.
|
|
11
|
+
*/
|
|
12
|
+
export declare class RelatedEntityGridPanelComponent {
|
|
13
|
+
Contribution: FormContributionWinner;
|
|
14
|
+
Record: BaseEntity;
|
|
15
|
+
FormComponent: BaseFormComponent;
|
|
16
|
+
FormContext?: FormContext;
|
|
17
|
+
onDataLoad(event: AfterDataLoadEventArgs): void;
|
|
18
|
+
get GridIcon(): string;
|
|
19
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RelatedEntityGridPanelComponent, never>;
|
|
20
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<RelatedEntityGridPanelComponent, "mj-related-entity-grid-panel", never, { "Contribution": { "alias": "Contribution"; "required": false; }; "Record": { "alias": "Record"; "required": false; }; "FormComponent": { "alias": "FormComponent"; "required": false; }; "FormContext": { "alias": "FormContext"; "required": false; }; }, {}, never, never, false, never>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=related-entity-grid-panel.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related-entity-grid-panel.component.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/related-entity-grid-panel.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;;AAElE;;;;GAIG;AACH,qBA0Ba,+BAA+B;IAC/B,YAAY,EAAG,sBAAsB,CAAC;IACtC,MAAM,EAAG,UAAU,CAAC;IACpB,aAAa,EAAG,iBAAiB,CAAC;IAClC,WAAW,CAAC,EAAE,WAAW,CAAC;IAE5B,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI;IAItD,IAAW,QAAQ,IAAI,MAAM,CAK5B;yCAfQ,+BAA+B;2CAA/B,+BAA+B;CAgB3C"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import * as i1 from "../panel/collapsible-panel.component";
|
|
4
|
+
import * as i2 from "../explorer-entity-data-grid.component";
|
|
5
|
+
function RelatedEntityGridPanelComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
6
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
7
|
+
i0.ɵɵelementStart(0, "mj-explorer-entity-data-grid", 2);
|
|
8
|
+
i0.ɵɵlistener("Navigate", function RelatedEntityGridPanelComponent_Conditional_1_Template_mj_explorer_entity_data_grid_Navigate_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.FormComponent.OnFormNavigate($event)); })("AfterDataLoad", function RelatedEntityGridPanelComponent_Conditional_1_Template_mj_explorer_entity_data_grid_AfterDataLoad_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDataLoad($event)); });
|
|
9
|
+
i0.ɵɵelementEnd();
|
|
10
|
+
} if (rf & 2) {
|
|
11
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
12
|
+
i0.ɵɵproperty("Params", ctx_r1.FormComponent.BuildRelationshipViewParamsByEntityName(ctx_r1.Contribution.RelatedEntity, ctx_r1.Contribution.RelatedJoinField))("NewRecordValues", ctx_r1.FormComponent.NewRecordValues(ctx_r1.Contribution.RelatedEntity, ctx_r1.Contribution.RelatedJoinField))("AllowLoad", ctx_r1.FormComponent.IsSectionExpanded(ctx_r1.Contribution.BakedSectionKey))("ShowToolbar", true);
|
|
13
|
+
} }
|
|
14
|
+
/**
|
|
15
|
+
* Stock related-entity grid the composer mounts when a DisplayInForm
|
|
16
|
+
* relationship is neither baked into the form template nor claimed by a
|
|
17
|
+
* registered panel. Mirrors the CodeGen EntityDataGrid template.
|
|
18
|
+
*/
|
|
19
|
+
export class RelatedEntityGridPanelComponent {
|
|
20
|
+
Contribution;
|
|
21
|
+
Record;
|
|
22
|
+
FormComponent;
|
|
23
|
+
FormContext;
|
|
24
|
+
onDataLoad(event) {
|
|
25
|
+
this.FormComponent.SetSectionRowCount(this.Contribution.BakedSectionKey, event.totalRowCount);
|
|
26
|
+
}
|
|
27
|
+
get GridIcon() {
|
|
28
|
+
const name = this.Contribution.RelatedEntity;
|
|
29
|
+
if (!name)
|
|
30
|
+
return 'fa fa-table';
|
|
31
|
+
const icon = this.FormComponent.ProviderToUse.EntityByName(name)?.Icon?.trim();
|
|
32
|
+
return icon || 'fa fa-table';
|
|
33
|
+
}
|
|
34
|
+
static ɵfac = function RelatedEntityGridPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RelatedEntityGridPanelComponent)(); };
|
|
35
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RelatedEntityGridPanelComponent, selectors: [["mj-related-entity-grid-panel"]], inputs: { Contribution: "Contribution", Record: "Record", FormComponent: "FormComponent", FormContext: "FormContext" }, standalone: false, decls: 2, vars: 7, consts: [["Variant", "related-entity", 3, "SectionKey", "SectionName", "Icon", "Form", "FormContext", "DefaultExpanded"], [3, "Params", "NewRecordValues", "AllowLoad", "ShowToolbar"], [3, "Navigate", "AfterDataLoad", "Params", "NewRecordValues", "AllowLoad", "ShowToolbar"]], template: function RelatedEntityGridPanelComponent_Template(rf, ctx) { if (rf & 1) {
|
|
36
|
+
i0.ɵɵelementStart(0, "mj-collapsible-panel", 0);
|
|
37
|
+
i0.ɵɵconditionalCreate(1, RelatedEntityGridPanelComponent_Conditional_1_Template, 1, 4, "mj-explorer-entity-data-grid", 1);
|
|
38
|
+
i0.ɵɵelementEnd();
|
|
39
|
+
} if (rf & 2) {
|
|
40
|
+
i0.ɵɵproperty("SectionKey", ctx.Contribution.BakedSectionKey)("SectionName", ctx.Contribution.DisplayName)("Icon", ctx.GridIcon)("Form", ctx.FormComponent)("FormContext", ctx.FormContext)("DefaultExpanded", false);
|
|
41
|
+
i0.ɵɵadvance();
|
|
42
|
+
i0.ɵɵconditional(ctx.Record.IsSaved ? 1 : -1);
|
|
43
|
+
} }, dependencies: [i1.MjCollapsiblePanelComponent, i2.ExplorerEntityDataGridComponent], styles: ["[_nghost-%COMP%] { display: contents; }"] });
|
|
44
|
+
}
|
|
45
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RelatedEntityGridPanelComponent, [{
|
|
46
|
+
type: Component,
|
|
47
|
+
args: [{ standalone: false, selector: 'mj-related-entity-grid-panel', template: `
|
|
48
|
+
<mj-collapsible-panel
|
|
49
|
+
[SectionKey]="Contribution.BakedSectionKey"
|
|
50
|
+
[SectionName]="Contribution.DisplayName"
|
|
51
|
+
[Icon]="GridIcon"
|
|
52
|
+
Variant="related-entity"
|
|
53
|
+
[Form]="FormComponent"
|
|
54
|
+
[FormContext]="FormContext"
|
|
55
|
+
[DefaultExpanded]="false">
|
|
56
|
+
@if (Record.IsSaved) {
|
|
57
|
+
<mj-explorer-entity-data-grid
|
|
58
|
+
[Params]="FormComponent.BuildRelationshipViewParamsByEntityName(Contribution.RelatedEntity!, Contribution.RelatedJoinField)"
|
|
59
|
+
[NewRecordValues]="FormComponent.NewRecordValues(Contribution.RelatedEntity!, Contribution.RelatedJoinField)"
|
|
60
|
+
[AllowLoad]="FormComponent.IsSectionExpanded(Contribution.BakedSectionKey)"
|
|
61
|
+
[ShowToolbar]="true"
|
|
62
|
+
(Navigate)="FormComponent.OnFormNavigate($event)"
|
|
63
|
+
(AfterDataLoad)="onDataLoad($event)">
|
|
64
|
+
</mj-explorer-entity-data-grid>
|
|
65
|
+
}
|
|
66
|
+
</mj-collapsible-panel>
|
|
67
|
+
`, styles: [":host { display: contents; }"] }]
|
|
68
|
+
}], null, { Contribution: [{
|
|
69
|
+
type: Input
|
|
70
|
+
}], Record: [{
|
|
71
|
+
type: Input
|
|
72
|
+
}], FormComponent: [{
|
|
73
|
+
type: Input
|
|
74
|
+
}], FormContext: [{
|
|
75
|
+
type: Input
|
|
76
|
+
}] }); })();
|
|
77
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RelatedEntityGridPanelComponent, { className: "RelatedEntityGridPanelComponent", filePath: "src/lib/panel-slot/related-entity-grid-panel.component.ts", lineNumber: 39 }); })();
|
|
78
|
+
//# sourceMappingURL=related-entity-grid-panel.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related-entity-grid-panel.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/related-entity-grid-panel.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;;;;;;IAyBjC,uDAMyC;IAArC,AADA,qOAAY,2CAAoC,KAAC,kOAChC,yBAAkB,KAAC;IACxC,iBAA+B;;;IAH3B,AADA,AADA,AADA,8JAA4H,kIACf,0FAClC,qBACvD;;AAtBxC;;;;GAIG;AA2BH,MAAM,OAAO,+BAA+B;IAC/B,YAAY,CAA0B;IACtC,MAAM,CAAc;IACpB,aAAa,CAAqB;IAClC,WAAW,CAAe;IAE5B,UAAU,CAAC,KAA6B;QAC3C,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAClG,CAAC;IAED,IAAW,QAAQ;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO,aAAa,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC/E,OAAO,IAAI,IAAI,aAAa,CAAC;IACjC,CAAC;yHAfQ,+BAA+B;6DAA/B,+BAA+B;YAtBpC,+CAO8B;YAC1B,0HAAsB;YAU1B,iBAAuB;;YAXnB,AADA,AADA,AAFA,AADA,AADA,6DAA2C,6CACH,sBACvB,2BAEK,gCACK,0BACF;YACzB,cASC;YATD,6CASC;;;iFAKA,+BAA+B;cA1B3C,SAAS;6BACM,KAAK,YACP,8BAA8B,YAC9B;;;;;;;;;;;;;;;;;;;;KAoBT;;kBAIA,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kFAJG,+BAA+B","sourcesContent":["import { Component, Input } from '@angular/core';\nimport { BaseEntity } from '@memberjunction/core';\nimport type { AfterDataLoadEventArgs } from '@memberjunction/ng-entity-viewer';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport type { FormContributionWinner } from './form-contribution';\n\n/**\n * Stock related-entity grid the composer mounts when a DisplayInForm\n * relationship is neither baked into the form template nor claimed by a\n * registered panel. Mirrors the CodeGen EntityDataGrid template.\n */\n@Component({\n standalone: false,\n selector: 'mj-related-entity-grid-panel',\n template: `\n <mj-collapsible-panel\n [SectionKey]=\"Contribution.BakedSectionKey\"\n [SectionName]=\"Contribution.DisplayName\"\n [Icon]=\"GridIcon\"\n Variant=\"related-entity\"\n [Form]=\"FormComponent\"\n [FormContext]=\"FormContext\"\n [DefaultExpanded]=\"false\">\n @if (Record.IsSaved) {\n <mj-explorer-entity-data-grid\n [Params]=\"FormComponent.BuildRelationshipViewParamsByEntityName(Contribution.RelatedEntity!, Contribution.RelatedJoinField)\"\n [NewRecordValues]=\"FormComponent.NewRecordValues(Contribution.RelatedEntity!, Contribution.RelatedJoinField)\"\n [AllowLoad]=\"FormComponent.IsSectionExpanded(Contribution.BakedSectionKey)\"\n [ShowToolbar]=\"true\"\n (Navigate)=\"FormComponent.OnFormNavigate($event)\"\n (AfterDataLoad)=\"onDataLoad($event)\">\n </mj-explorer-entity-data-grid>\n }\n </mj-collapsible-panel>\n `,\n styles: [`:host { display: contents; }`],\n})\nexport class RelatedEntityGridPanelComponent {\n @Input() Contribution!: FormContributionWinner;\n @Input() Record!: BaseEntity;\n @Input() FormComponent!: BaseFormComponent;\n @Input() FormContext?: FormContext;\n\n public onDataLoad(event: AfterDataLoadEventArgs): void {\n this.FormComponent.SetSectionRowCount(this.Contribution.BakedSectionKey, event.totalRowCount);\n }\n\n public get GridIcon(): string {\n const name = this.Contribution.RelatedEntity;\n if (!name) return 'fa fa-table';\n const icon = this.FormComponent.ProviderToUse.EntityByName(name)?.Icon?.trim();\n return icon || 'fa fa-table';\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Toolbar row (search + actions). */
|
|
2
|
+
export declare const RELATED_GRID_TOOLBAR_PX = 52;
|
|
3
|
+
/** Column header row. */
|
|
4
|
+
export declare const RELATED_GRID_HEADER_PX = 40;
|
|
5
|
+
/** One data row. */
|
|
6
|
+
export declare const RELATED_GRID_ROW_PX = 40;
|
|
7
|
+
/** 4–5px so the last row's bottom border is not clipped by the grid edge. */
|
|
8
|
+
export declare const RELATED_GRID_BOTTOM_PAD_PX = 5;
|
|
9
|
+
/** Empty-state body when there are no rows (inline icon + title, no 200px floor). */
|
|
10
|
+
export declare const RELATED_GRID_EMPTY_BODY_PX = 56;
|
|
11
|
+
/** Default cap for nav-related grids. `null` on the input means unbounded. */
|
|
12
|
+
export declare const RELATED_GRID_DEFAULT_MAX_PX = 560;
|
|
13
|
+
/**
|
|
14
|
+
* Pixel height for a related-entity grid: toolbar + header + rows + a
|
|
15
|
+
* small bottom pad. No minimum floor. When `maxHeight` is a positive
|
|
16
|
+
* number and the content is taller, the returned height is that cap and
|
|
17
|
+
* AG Grid scrolls inside. Omit / null `maxHeight` to grow with the rows.
|
|
18
|
+
*/
|
|
19
|
+
export declare function RelatedGridHeightPx(rowCount: number, maxHeight?: number | null): number;
|
|
20
|
+
//# sourceMappingURL=related-grid-height.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related-grid-height.d.ts","sourceRoot":"","sources":["../../src/lib/related-grid-height.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,yBAAyB;AACzB,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,oBAAoB;AACpB,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,6EAA6E;AAC7E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,qFAAqF;AACrF,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,8EAA8E;AAC9E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAUvF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Toolbar row (search + actions). */
|
|
2
|
+
export const RELATED_GRID_TOOLBAR_PX = 52;
|
|
3
|
+
/** Column header row. */
|
|
4
|
+
export const RELATED_GRID_HEADER_PX = 40;
|
|
5
|
+
/** One data row. */
|
|
6
|
+
export const RELATED_GRID_ROW_PX = 40;
|
|
7
|
+
/** 4–5px so the last row's bottom border is not clipped by the grid edge. */
|
|
8
|
+
export const RELATED_GRID_BOTTOM_PAD_PX = 5;
|
|
9
|
+
/** Empty-state body when there are no rows (inline icon + title, no 200px floor). */
|
|
10
|
+
export const RELATED_GRID_EMPTY_BODY_PX = 56;
|
|
11
|
+
/** Default cap for nav-related grids. `null` on the input means unbounded. */
|
|
12
|
+
export const RELATED_GRID_DEFAULT_MAX_PX = 560;
|
|
13
|
+
/**
|
|
14
|
+
* Pixel height for a related-entity grid: toolbar + header + rows + a
|
|
15
|
+
* small bottom pad. No minimum floor. When `maxHeight` is a positive
|
|
16
|
+
* number and the content is taller, the returned height is that cap and
|
|
17
|
+
* AG Grid scrolls inside. Omit / null `maxHeight` to grow with the rows.
|
|
18
|
+
*/
|
|
19
|
+
export function RelatedGridHeightPx(rowCount, maxHeight) {
|
|
20
|
+
const rows = Number.isFinite(rowCount) ? Math.max(0, Math.floor(rowCount)) : 0;
|
|
21
|
+
const body = rows === 0
|
|
22
|
+
? RELATED_GRID_EMPTY_BODY_PX
|
|
23
|
+
: RELATED_GRID_HEADER_PX + rows * RELATED_GRID_ROW_PX;
|
|
24
|
+
const raw = RELATED_GRID_TOOLBAR_PX + body + RELATED_GRID_BOTTOM_PAD_PX;
|
|
25
|
+
if (typeof maxHeight === 'number' && maxHeight > 0) {
|
|
26
|
+
return Math.min(maxHeight, raw);
|
|
27
|
+
}
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=related-grid-height.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related-grid-height.js","sourceRoot":"","sources":["../../src/lib/related-grid-height.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,yBAAyB;AACzB,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,oBAAoB;AACpB,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AACtC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAC5C,qFAAqF;AACrF,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAC7C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,SAAyB;IAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC;QACnB,CAAC,CAAC,0BAA0B;QAC5B,CAAC,CAAC,sBAAsB,GAAG,IAAI,GAAG,mBAAmB,CAAC;IAC1D,MAAM,GAAG,GAAG,uBAAuB,GAAG,IAAI,GAAG,0BAA0B,CAAC;IACxE,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC","sourcesContent":["/** Toolbar row (search + actions). */\nexport const RELATED_GRID_TOOLBAR_PX = 52;\n/** Column header row. */\nexport const RELATED_GRID_HEADER_PX = 40;\n/** One data row. */\nexport const RELATED_GRID_ROW_PX = 40;\n/** 4–5px so the last row's bottom border is not clipped by the grid edge. */\nexport const RELATED_GRID_BOTTOM_PAD_PX = 5;\n/** Empty-state body when there are no rows (inline icon + title, no 200px floor). */\nexport const RELATED_GRID_EMPTY_BODY_PX = 56;\n/** Default cap for nav-related grids. `null` on the input means unbounded. */\nexport const RELATED_GRID_DEFAULT_MAX_PX = 560;\n\n/**\n * Pixel height for a related-entity grid: toolbar + header + rows + a\n * small bottom pad. No minimum floor. When `maxHeight` is a positive\n * number and the content is taller, the returned height is that cap and\n * AG Grid scrolls inside. Omit / null `maxHeight` to grow with the rows.\n */\nexport function RelatedGridHeightPx(rowCount: number, maxHeight?: number | null): number {\n const rows = Number.isFinite(rowCount) ? Math.max(0, Math.floor(rowCount)) : 0;\n const body = rows === 0\n ? RELATED_GRID_EMPTY_BODY_PX\n : RELATED_GRID_HEADER_PX + rows * RELATED_GRID_ROW_PX;\n const raw = RELATED_GRID_TOOLBAR_PX + body + RELATED_GRID_BOTTOM_PAD_PX;\n if (typeof maxHeight === 'number' && maxHeight > 0) {\n return Math.min(maxHeight, raw);\n }\n return raw;\n}\n"]}
|
|
@@ -10,6 +10,10 @@ export interface SectionManagerItem {
|
|
|
10
10
|
Variant: PanelVariant;
|
|
11
11
|
Icon: string;
|
|
12
12
|
}
|
|
13
|
+
export interface ChromeMembershipChange {
|
|
14
|
+
moreSectionKeys: string[];
|
|
15
|
+
firstClassSectionKeys: string[];
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Slide-in drawer for managing section order.
|
|
15
19
|
*
|
|
@@ -34,27 +38,42 @@ export declare class MjSectionManagerComponent implements OnChanges {
|
|
|
34
38
|
Sections: SectionManagerItem[];
|
|
35
39
|
/** Current section order (array of section keys) */
|
|
36
40
|
SectionOrder: string[];
|
|
41
|
+
/** Section keys currently in More. Empty keeps a single list. */
|
|
42
|
+
MoreSectionKeys: string[];
|
|
43
|
+
/** Keys that cannot leave More (System Metadata). */
|
|
44
|
+
LockedMoreKeys: string[];
|
|
37
45
|
/** Whether the drawer is visible */
|
|
38
46
|
Visible: boolean;
|
|
39
47
|
/** Emits the new section order when the user reorders */
|
|
40
48
|
SectionOrderChange: EventEmitter<string[]>;
|
|
49
|
+
/** Emits when the user moves a section in or out of More. */
|
|
50
|
+
MembershipChange: EventEmitter<ChromeMembershipChange>;
|
|
41
51
|
/** Emits when the drawer is closed */
|
|
42
52
|
Closed: EventEmitter<void>;
|
|
43
53
|
/** Emits when the user resets to default order */
|
|
44
54
|
ResetRequested: EventEmitter<void>;
|
|
45
55
|
/** Ordered list of sections for display */
|
|
46
56
|
OrderedSections: SectionManagerItem[];
|
|
57
|
+
FirstClassSections: SectionManagerItem[];
|
|
58
|
+
MoreSections: SectionManagerItem[];
|
|
47
59
|
ngOnChanges(changes: SimpleChanges): void;
|
|
60
|
+
get HasMoreGroup(): boolean;
|
|
61
|
+
IsLockedInMore(section: SectionManagerItem): boolean;
|
|
48
62
|
OnClose(): void;
|
|
49
63
|
OnOverlayClick(event: MouseEvent): void;
|
|
50
64
|
OnMoveUp(index: number): void;
|
|
51
65
|
OnMoveDown(index: number): void;
|
|
66
|
+
OnMoveGroupUp(list: SectionManagerItem[], index: number): void;
|
|
67
|
+
OnMoveGroupDown(list: SectionManagerItem[], index: number): void;
|
|
68
|
+
OnMoveToMore(section: SectionManagerItem): void;
|
|
69
|
+
OnMoveToForm(section: SectionManagerItem): void;
|
|
70
|
+
private emitReorderedGroup;
|
|
52
71
|
OnReset(): void;
|
|
53
72
|
GetVariantIcon(variant: PanelVariant): string;
|
|
54
73
|
GetVariantLabel(variant: PanelVariant): string;
|
|
55
74
|
private RebuildOrderedSections;
|
|
56
75
|
private ApplyOrder;
|
|
57
76
|
static ɵfac: i0.ɵɵFactoryDeclaration<MjSectionManagerComponent, never>;
|
|
58
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<MjSectionManagerComponent, "mj-section-manager", never, { "Sections": { "alias": "Sections"; "required": false; }; "SectionOrder": { "alias": "SectionOrder"; "required": false; }; "Visible": { "alias": "Visible"; "required": false; }; }, { "SectionOrderChange": "SectionOrderChange"; "Closed": "Closed"; "ResetRequested": "ResetRequested"; }, never, never, false, never>;
|
|
77
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<MjSectionManagerComponent, "mj-section-manager", never, { "Sections": { "alias": "Sections"; "required": false; }; "SectionOrder": { "alias": "SectionOrder"; "required": false; }; "MoreSectionKeys": { "alias": "MoreSectionKeys"; "required": false; }; "LockedMoreKeys": { "alias": "LockedMoreKeys"; "required": false; }; "Visible": { "alias": "Visible"; "required": false; }; }, { "SectionOrderChange": "SectionOrderChange"; "MembershipChange": "MembershipChange"; "Closed": "Closed"; "ResetRequested": "ResetRequested"; }, never, never, false, never>;
|
|
59
78
|
}
|
|
60
79
|
//# sourceMappingURL=section-manager.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-manager.component.d.ts","sourceRoot":"","sources":["../../../src/lib/section-manager/section-manager.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACqB,YAAY,EAEtC,SAAS,EAAE,aAAa,EACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;;AAEnD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAOa,yBAA0B,YAAW,SAAS;IACzD,OAAO,CAAC,GAAG,CAA6B;IAExC,yCAAyC;IAChC,QAAQ,EAAE,kBAAkB,EAAE,CAAM;IAE7C,oDAAoD;IAC3C,YAAY,EAAE,MAAM,EAAE,CAAM;IAErC,oCAAoC;IAC3B,OAAO,UAAS;IAEzB,yDAAyD;IAC/C,kBAAkB,yBAAgC;IAE5D,sCAAsC;IAC5B,MAAM,qBAA4B;IAE5C,kDAAkD;IACxC,cAAc,qBAA4B;IAEpD,2CAA2C;IAC3C,eAAe,EAAE,kBAAkB,EAAE,CAAM;
|
|
1
|
+
{"version":3,"file":"section-manager.component.d.ts","sourceRoot":"","sources":["../../../src/lib/section-manager/section-manager.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACqB,YAAY,EAEtC,SAAS,EAAE,aAAa,EACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;;AAEnD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAOa,yBAA0B,YAAW,SAAS;IACzD,OAAO,CAAC,GAAG,CAA6B;IAExC,yCAAyC;IAChC,QAAQ,EAAE,kBAAkB,EAAE,CAAM;IAE7C,oDAAoD;IAC3C,YAAY,EAAE,MAAM,EAAE,CAAM;IAErC,iEAAiE;IACxD,eAAe,EAAE,MAAM,EAAE,CAAM;IAExC,qDAAqD;IAC5C,cAAc,EAAE,MAAM,EAAE,CAAM;IAEvC,oCAAoC;IAC3B,OAAO,UAAS;IAEzB,yDAAyD;IAC/C,kBAAkB,yBAAgC;IAE5D,6DAA6D;IACnD,gBAAgB,uCAA8C;IAExE,sCAAsC;IAC5B,MAAM,qBAA4B;IAE5C,kDAAkD;IACxC,cAAc,qBAA4B;IAEpD,2CAA2C;IAC3C,eAAe,EAAE,kBAAkB,EAAE,CAAM;IAC3C,kBAAkB,EAAE,kBAAkB,EAAE,CAAM;IAC9C,YAAY,EAAE,kBAAkB,EAAE,CAAM;IAExC,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAMzC,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO;IAIpD,OAAO,IAAI,IAAI;IAIf,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAOvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ/B,aAAa,CAAC,IAAI,EAAE,kBAAkB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAK9D,eAAe,CAAC,IAAI,EAAE,kBAAkB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAKhE,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAS/C,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAU/C,OAAO,CAAC,kBAAkB;IAW1B,OAAO,IAAI,IAAI;IAIf,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM;IAQ7C,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM;IAQ9C,OAAO,CAAC,sBAAsB;IAoC9B,OAAO,CAAC,UAAU;yCA5KP,yBAAyB;2CAAzB,yBAAyB;CAyLrC"}
|