@memberjunction/ng-base-forms 5.36.0 → 5.38.0
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/dist/lib/base-form-component.d.ts +28 -0
- package/dist/lib/base-form-component.d.ts.map +1 -1
- package/dist/lib/base-form-component.js +45 -1
- package/dist/lib/base-form-component.js.map +1 -1
- package/dist/lib/container/record-form-container.component.d.ts +63 -1
- package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
- package/dist/lib/container/record-form-container.component.js +134 -42
- package/dist/lib/container/record-form-container.component.js.map +1 -1
- package/dist/lib/interactive-form/interactive-form.component.d.ts +181 -0
- package/dist/lib/interactive-form/interactive-form.component.d.ts.map +1 -0
- package/dist/lib/interactive-form/interactive-form.component.js +560 -0
- package/dist/lib/interactive-form/interactive-form.component.js.map +1 -0
- package/dist/lib/panel-slot/base-form-panel.d.ts +97 -0
- package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -0
- package/dist/lib/panel-slot/base-form-panel.js +64 -0
- package/dist/lib/panel-slot/base-form-panel.js.map +1 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts +78 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-panel-slot.component.js +227 -0
- package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -0
- package/dist/lib/panel-slot/form-slot-coordinator.service.d.ts +57 -0
- package/dist/lib/panel-slot/form-slot-coordinator.service.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-slot-coordinator.service.js +104 -0
- package/dist/lib/panel-slot/form-slot-coordinator.service.js.map +1 -0
- package/dist/lib/toolbar/form-toolbar.component.d.ts +56 -1
- package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
- package/dist/lib/toolbar/form-toolbar.component.js +294 -136
- package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
- package/dist/lib/types/navigation-events.d.ts +19 -1
- package/dist/lib/types/navigation-events.d.ts.map +1 -1
- package/dist/lib/types/navigation-events.js.map +1 -1
- package/dist/lib/types/toolbar-config.d.ts +11 -0
- package/dist/lib/types/toolbar-config.d.ts.map +1 -1
- package/dist/lib/types/toolbar-config.js +3 -0
- package/dist/lib/types/toolbar-config.js.map +1 -1
- package/dist/module.d.ts +10 -7
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +21 -7
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +7 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +12 -10
- package/dist/__tests__/sanity.test.d.ts +0 -2
- package/dist/__tests__/sanity.test.d.ts.map +0 -1
- package/dist/__tests__/sanity.test.js +0 -12
- package/dist/__tests__/sanity.test.js.map +0 -1
- package/dist/__tests__/tag-count-badge.test.d.ts +0 -2
- package/dist/__tests__/tag-count-badge.test.d.ts.map +0 -1
- package/dist/__tests__/tag-count-badge.test.js +0 -70
- package/dist/__tests__/tag-count-badge.test.js.map +0 -1
- package/dist/lib/__tests__/base-forms.test.d.ts +0 -2
- package/dist/lib/__tests__/base-forms.test.d.ts.map +0 -1
- package/dist/lib/__tests__/base-forms.test.js +0 -256
- package/dist/lib/__tests__/base-forms.test.js.map +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BaseEntity, ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { BaseFormComponent } from '../base-form-component';
|
|
3
|
+
import { FormContext } from '../types/form-types';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* Well-known slot positions where panels can be injected into a generated
|
|
7
|
+
* entity form. Used as the `slot` field in a panel's registration metadata.
|
|
8
|
+
*
|
|
9
|
+
* Slot positions inside the form, top to bottom:
|
|
10
|
+
*
|
|
11
|
+
* ┌─ <mj-record-form-container>
|
|
12
|
+
* │ [top-area slot] ← CodeGen-emitted; rare (entities with top-area section)
|
|
13
|
+
* │ [before-fields slot] ← CodeGen-emitted; banners / warnings above field panels
|
|
14
|
+
* │ <connection details>
|
|
15
|
+
* │ <other generated field panels>
|
|
16
|
+
* │ [after-fields slot] ← CodeGen-emitted; typed config knobs, secondary settings — most common slot
|
|
17
|
+
* │ <content items grid>
|
|
18
|
+
* │ <other related-entity grids>
|
|
19
|
+
* │ [after-related slot] ← CodeGen-emitted; bottom-of-form addenda
|
|
20
|
+
* │ [after-everything slot] ← Container-emitted; ALWAYS present (fallback target)
|
|
21
|
+
* └─ </mj-record-form-container>
|
|
22
|
+
*
|
|
23
|
+
* Pick the slot that matches the panel's role. `after-fields` is the default
|
|
24
|
+
* for most "additional settings" panels — it sits in the field column, before
|
|
25
|
+
* the related-entity grids.
|
|
26
|
+
*
|
|
27
|
+
* **Fallback behavior** — see `FormSlotCoordinator`. If the registered slot is
|
|
28
|
+
* missing in the current form (CodeGen hasn't been rerun against the new
|
|
29
|
+
* template emitter), the panel walks forward in the chain until it finds an
|
|
30
|
+
* existing slot. `after-everything` is always emitted by the container, so
|
|
31
|
+
* fallback never dead-ends. Pre-CodeGen-regen forms show every panel at the
|
|
32
|
+
* bottom of the form; post-regen forms show them in the preferred position.
|
|
33
|
+
*/
|
|
34
|
+
export type FormPanelSlot = 'top-area' | 'before-fields' | 'after-fields' | 'after-related' | 'after-everything';
|
|
35
|
+
/**
|
|
36
|
+
* Shape of the `metadata` object every BaseFormPanel registration MUST attach
|
|
37
|
+
* via `@RegisterClass(BaseFormPanel, { metadata: {...} })`. The
|
|
38
|
+
* `<mj-form-panel-slot>` host filters registrations by matching against this
|
|
39
|
+
* shape — registrations without it are silently ignored.
|
|
40
|
+
*
|
|
41
|
+
* - `entity` — the `MJContentSourceEntity_IContentSourceConfiguration`-style
|
|
42
|
+
* entity name. Must match the entity-form's record entity exactly
|
|
43
|
+
* (case-sensitive trim-equality enforced by the slot host).
|
|
44
|
+
* - `slot` — which slot the panel renders into.
|
|
45
|
+
* - `sortKey`— higher = earlier within the slot. Defaults to 0 if omitted.
|
|
46
|
+
* Use ranges (e.g., 100/50/10) so future panels can wedge in
|
|
47
|
+
* without recomputing every neighbor's sort.
|
|
48
|
+
*/
|
|
49
|
+
export interface FormPanelRegistrationMetadata extends Record<string, unknown> {
|
|
50
|
+
entity: string;
|
|
51
|
+
slot: FormPanelSlot;
|
|
52
|
+
sortKey?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Abstract base for every dynamically-injectable form panel.
|
|
56
|
+
*
|
|
57
|
+
* Subclasses are standalone Angular components decorated with
|
|
58
|
+
* `@RegisterClass(BaseFormPanel, { metadata: { entity, slot, sortKey? } })`.
|
|
59
|
+
* At runtime, `<mj-form-panel-slot>` discovers matching registrations via
|
|
60
|
+
* `ClassFactory.GetAllRegistrationsByMetadata`, instantiates each via
|
|
61
|
+
* `ViewContainerRef.createComponent`, and wires `Record` + `FormComponent` +
|
|
62
|
+
* `FormContext` into the inputs.
|
|
63
|
+
*
|
|
64
|
+
* Lifecycle hooks: standard Angular `ngOnInit` / `ngOnDestroy` work as usual.
|
|
65
|
+
* `Record` is guaranteed to be set before the first change-detection pass.
|
|
66
|
+
*
|
|
67
|
+
* Optional `validate()` returns a synchronous validation result; the parent
|
|
68
|
+
* `BaseFormComponent.Save()` path will surface it via the existing validation
|
|
69
|
+
* pipeline when called. Panels that don't validate anything beyond what the
|
|
70
|
+
* record itself does can leave this method off.
|
|
71
|
+
*/
|
|
72
|
+
export declare abstract class BaseFormPanel<TRecord extends BaseEntity = BaseEntity> {
|
|
73
|
+
/** The entity record being edited. Set by the slot host before view init. */
|
|
74
|
+
Record: TRecord;
|
|
75
|
+
/** The host form component (use for EditMode, dirty notifications, etc). Set by the slot host. */
|
|
76
|
+
FormComponent: BaseFormComponent;
|
|
77
|
+
/** Optional form context — same shape the collapsible-panel chrome expects. Set by the slot host. */
|
|
78
|
+
FormContext?: FormContext;
|
|
79
|
+
/**
|
|
80
|
+
* Convenience getter for read-only / edit-mode rendering. Falls back to
|
|
81
|
+
* `false` when the panel is shown outside a `BaseFormComponent` host (e.g.,
|
|
82
|
+
* a dashboard quick-edit dialog reusing the same panel — see the
|
|
83
|
+
* "composable panel" docs).
|
|
84
|
+
*/
|
|
85
|
+
get EditMode(): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Override to add per-panel validation that should block the parent form's
|
|
88
|
+
* Save(). Return `{ Success: true, Errors: [] }` when valid, or include
|
|
89
|
+
* `ValidationErrorInfo` entries to surface field-level errors. The default
|
|
90
|
+
* implementation reports valid (panels that don't need extra validation
|
|
91
|
+
* can leave this method off).
|
|
92
|
+
*/
|
|
93
|
+
validate(): ValidationResult;
|
|
94
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<BaseFormPanel<any>, never>;
|
|
95
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<BaseFormPanel<any>, never, never, { "Record": { "alias": "Record"; "required": false; }; "FormComponent": { "alias": "FormComponent"; "required": false; }; "FormContext": { "alias": "FormContext"; "required": false; }; }, {}, never, never, true, never>;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=base-form-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-form-panel.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/base-form-panel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,aAAa,GACnB,UAAU,GACV,eAAe,GACf,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEzB;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,6BAA8B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,8BACsB,aAAa,CAAC,OAAO,SAAS,UAAU,GAAG,UAAU;IACvE,6EAA6E;IACpE,MAAM,EAAG,OAAO,CAAC;IAC1B,kGAAkG;IACzF,aAAa,EAAG,iBAAiB,CAAC;IAC3C,qGAAqG;IAC5F,WAAW,CAAC,EAAE,WAAW,CAAC;IAEnC;;;;;OAKG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;;;;;OAMG;IACI,QAAQ,IAAI,gBAAgB;yCAzBjB,aAAa;2CAAb,aAAa;CAgClC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Directive, Input } from '@angular/core';
|
|
2
|
+
import { ValidationResult } from '@memberjunction/core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base for every dynamically-injectable form panel.
|
|
6
|
+
*
|
|
7
|
+
* Subclasses are standalone Angular components decorated with
|
|
8
|
+
* `@RegisterClass(BaseFormPanel, { metadata: { entity, slot, sortKey? } })`.
|
|
9
|
+
* At runtime, `<mj-form-panel-slot>` discovers matching registrations via
|
|
10
|
+
* `ClassFactory.GetAllRegistrationsByMetadata`, instantiates each via
|
|
11
|
+
* `ViewContainerRef.createComponent`, and wires `Record` + `FormComponent` +
|
|
12
|
+
* `FormContext` into the inputs.
|
|
13
|
+
*
|
|
14
|
+
* Lifecycle hooks: standard Angular `ngOnInit` / `ngOnDestroy` work as usual.
|
|
15
|
+
* `Record` is guaranteed to be set before the first change-detection pass.
|
|
16
|
+
*
|
|
17
|
+
* Optional `validate()` returns a synchronous validation result; the parent
|
|
18
|
+
* `BaseFormComponent.Save()` path will surface it via the existing validation
|
|
19
|
+
* pipeline when called. Panels that don't validate anything beyond what the
|
|
20
|
+
* record itself does can leave this method off.
|
|
21
|
+
*/
|
|
22
|
+
export class BaseFormPanel {
|
|
23
|
+
/** The entity record being edited. Set by the slot host before view init. */
|
|
24
|
+
Record;
|
|
25
|
+
/** The host form component (use for EditMode, dirty notifications, etc). Set by the slot host. */
|
|
26
|
+
FormComponent;
|
|
27
|
+
/** Optional form context — same shape the collapsible-panel chrome expects. Set by the slot host. */
|
|
28
|
+
FormContext;
|
|
29
|
+
/**
|
|
30
|
+
* Convenience getter for read-only / edit-mode rendering. Falls back to
|
|
31
|
+
* `false` when the panel is shown outside a `BaseFormComponent` host (e.g.,
|
|
32
|
+
* a dashboard quick-edit dialog reusing the same panel — see the
|
|
33
|
+
* "composable panel" docs).
|
|
34
|
+
*/
|
|
35
|
+
get EditMode() {
|
|
36
|
+
return this.FormComponent?.EditMode ?? false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Override to add per-panel validation that should block the parent form's
|
|
40
|
+
* Save(). Return `{ Success: true, Errors: [] }` when valid, or include
|
|
41
|
+
* `ValidationErrorInfo` entries to surface field-level errors. The default
|
|
42
|
+
* implementation reports valid (panels that don't need extra validation
|
|
43
|
+
* can leave this method off).
|
|
44
|
+
*/
|
|
45
|
+
validate() {
|
|
46
|
+
// Inline construction — ValidationResult is a class in @memberjunction/core,
|
|
47
|
+
// not a plain interface, so callers can construct via `new`.
|
|
48
|
+
const result = new ValidationResult();
|
|
49
|
+
result.Success = true;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
static ɵfac = function BaseFormPanel_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BaseFormPanel)(); };
|
|
53
|
+
static ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseFormPanel, inputs: { Record: "Record", FormComponent: "FormComponent", FormContext: "FormContext" } });
|
|
54
|
+
}
|
|
55
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseFormPanel, [{
|
|
56
|
+
type: Directive
|
|
57
|
+
}], null, { Record: [{
|
|
58
|
+
type: Input
|
|
59
|
+
}], FormComponent: [{
|
|
60
|
+
type: Input
|
|
61
|
+
}], FormContext: [{
|
|
62
|
+
type: Input
|
|
63
|
+
}] }); })();
|
|
64
|
+
//# sourceMappingURL=base-form-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-form-panel.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/base-form-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAc,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;;AA4DpE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,OAAgB,aAAa;IAC/B,6EAA6E;IACpE,MAAM,CAAW;IAC1B,kGAAkG;IACzF,aAAa,CAAqB;IAC3C,qGAAqG;IAC5F,WAAW,CAAe;IAEnC;;;;;OAKG;IACH,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,aAAa,EAAE,QAAQ,IAAI,KAAK,CAAC;IACjD,CAAC;IAED;;;;;;OAMG;IACI,QAAQ;QACX,6EAA6E;QAC7E,6DAA6D;QAC7D,MAAM,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,OAAO,MAAM,CAAC;IAClB,CAAC;uGA/BiB,aAAa;6DAAb,aAAa;;iFAAb,aAAa;cADlC,SAAS;;kBAGL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK","sourcesContent":["import { Directive, Input } from '@angular/core';\nimport { BaseEntity, ValidationResult } from '@memberjunction/core';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\n\n/**\n * Well-known slot positions where panels can be injected into a generated\n * entity form. Used as the `slot` field in a panel's registration metadata.\n *\n * Slot positions inside the form, top to bottom:\n *\n * ┌─ <mj-record-form-container>\n * │ [top-area slot] ← CodeGen-emitted; rare (entities with top-area section)\n * │ [before-fields slot] ← CodeGen-emitted; banners / warnings above field panels\n * │ <connection details>\n * │ <other generated field panels>\n * │ [after-fields slot] ← CodeGen-emitted; typed config knobs, secondary settings — most common slot\n * │ <content items grid>\n * │ <other related-entity grids>\n * │ [after-related slot] ← CodeGen-emitted; bottom-of-form addenda\n * │ [after-everything slot] ← Container-emitted; ALWAYS present (fallback target)\n * └─ </mj-record-form-container>\n *\n * Pick the slot that matches the panel's role. `after-fields` is the default\n * for most \"additional settings\" panels — it sits in the field column, before\n * the related-entity grids.\n *\n * **Fallback behavior** — see `FormSlotCoordinator`. If the registered slot is\n * missing in the current form (CodeGen hasn't been rerun against the new\n * template emitter), the panel walks forward in the chain until it finds an\n * existing slot. `after-everything` is always emitted by the container, so\n * fallback never dead-ends. Pre-CodeGen-regen forms show every panel at the\n * bottom of the form; post-regen forms show them in the preferred position.\n */\nexport type FormPanelSlot =\n | 'top-area'\n | 'before-fields'\n | 'after-fields'\n | 'after-related'\n | 'after-everything';\n\n/**\n * Shape of the `metadata` object every BaseFormPanel registration MUST attach\n * via `@RegisterClass(BaseFormPanel, { metadata: {...} })`. The\n * `<mj-form-panel-slot>` host filters registrations by matching against this\n * shape — registrations without it are silently ignored.\n *\n * - `entity` — the `MJContentSourceEntity_IContentSourceConfiguration`-style\n * entity name. Must match the entity-form's record entity exactly\n * (case-sensitive trim-equality enforced by the slot host).\n * - `slot` — which slot the panel renders into.\n * - `sortKey`— higher = earlier within the slot. Defaults to 0 if omitted.\n * Use ranges (e.g., 100/50/10) so future panels can wedge in\n * without recomputing every neighbor's sort.\n */\nexport interface FormPanelRegistrationMetadata extends Record<string, unknown> {\n entity: string;\n slot: FormPanelSlot;\n sortKey?: number;\n}\n\n/**\n * Abstract base for every dynamically-injectable form panel.\n *\n * Subclasses are standalone Angular components decorated with\n * `@RegisterClass(BaseFormPanel, { metadata: { entity, slot, sortKey? } })`.\n * At runtime, `<mj-form-panel-slot>` discovers matching registrations via\n * `ClassFactory.GetAllRegistrationsByMetadata`, instantiates each via\n * `ViewContainerRef.createComponent`, and wires `Record` + `FormComponent` +\n * `FormContext` into the inputs.\n *\n * Lifecycle hooks: standard Angular `ngOnInit` / `ngOnDestroy` work as usual.\n * `Record` is guaranteed to be set before the first change-detection pass.\n *\n * Optional `validate()` returns a synchronous validation result; the parent\n * `BaseFormComponent.Save()` path will surface it via the existing validation\n * pipeline when called. Panels that don't validate anything beyond what the\n * record itself does can leave this method off.\n */\n@Directive()\nexport abstract class BaseFormPanel<TRecord extends BaseEntity = BaseEntity> {\n /** The entity record being edited. Set by the slot host before view init. */\n @Input() Record!: TRecord;\n /** The host form component (use for EditMode, dirty notifications, etc). Set by the slot host. */\n @Input() FormComponent!: BaseFormComponent;\n /** Optional form context — same shape the collapsible-panel chrome expects. Set by the slot host. */\n @Input() FormContext?: FormContext;\n\n /**\n * Convenience getter for read-only / edit-mode rendering. Falls back to\n * `false` when the panel is shown outside a `BaseFormComponent` host (e.g.,\n * a dashboard quick-edit dialog reusing the same panel — see the\n * \"composable panel\" docs).\n */\n public get EditMode(): boolean {\n return this.FormComponent?.EditMode ?? false;\n }\n\n /**\n * Override to add per-panel validation that should block the parent form's\n * Save(). Return `{ Success: true, Errors: [] }` when valid, or include\n * `ValidationErrorInfo` entries to surface field-level errors. The default\n * implementation reports valid (panels that don't need extra validation\n * can leave this method off).\n */\n public validate(): ValidationResult {\n // Inline construction — ValidationResult is a class in @memberjunction/core,\n // not a plain interface, so callers can construct via `new`.\n const result = new ValidationResult();\n result.Success = true;\n return result;\n }\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
|
2
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
3
|
+
import { BaseFormComponent } from '../base-form-component';
|
|
4
|
+
import { FormContext } from '../types/form-types';
|
|
5
|
+
import { FormPanelSlot } from './base-form-panel';
|
|
6
|
+
import { FormSlotCoordinator } from './form-slot-coordinator.service';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
/**
|
|
9
|
+
* `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every
|
|
10
|
+
* registered `BaseFormPanel` whose metadata matches the slot's `Entity` +
|
|
11
|
+
* `Slot` inputs. Generated entity-form HTML emits one of these at each
|
|
12
|
+
* well-known slot position so consumers can extend the form WITHOUT replacing
|
|
13
|
+
* it via the custom-form override pattern.
|
|
14
|
+
*
|
|
15
|
+
* **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`
|
|
16
|
+
* to find every panel registered with metadata `{ entity, slot }` matching this
|
|
17
|
+
* slot. Registrations without a metadata bag (or without these required keys)
|
|
18
|
+
* are silently ignored so legacy registrations don't accidentally bind.
|
|
19
|
+
*
|
|
20
|
+
* **Ordering**: registrations are sorted by `metadata.sortKey` descending —
|
|
21
|
+
* higher number renders first within the slot. Use ranges (100/50/10) so future
|
|
22
|
+
* panels can wedge in without renumbering. Ties fall back to the
|
|
23
|
+
* `ClassRegistration.Priority` field (also descending), and finally to
|
|
24
|
+
* registration order.
|
|
25
|
+
*
|
|
26
|
+
* **Fallback chain** (see `FormSlotCoordinator`): each slot host registers
|
|
27
|
+
* with the per-form coordinator and additionally renders panels whose
|
|
28
|
+
* REGISTERED slot is missing in the current form, when this host happens to
|
|
29
|
+
* be the next-existing slot down the chain. The container template guarantees
|
|
30
|
+
* an `after-everything` slot host, so fallback always terminates somewhere.
|
|
31
|
+
* Downstream consumers running an older form template (no CodeGen rerun
|
|
32
|
+
* yet) get every panel rendered at the bottom; consumers on the new template
|
|
33
|
+
* get them in the preferred position.
|
|
34
|
+
*
|
|
35
|
+
* **Re-mount**: any input change OR a coordinator change (another slot
|
|
36
|
+
* registers/deregisters) triggers a re-render so fallback assignments stay
|
|
37
|
+
* correct as slots come and go.
|
|
38
|
+
*/
|
|
39
|
+
export declare class FormPanelSlotComponent implements OnInit, OnChanges, OnDestroy {
|
|
40
|
+
private readonly coordinator?;
|
|
41
|
+
/** The entity name (e.g., `"MJ: Content Sources"`) being edited. */
|
|
42
|
+
Entity: string;
|
|
43
|
+
/** Which slot position this host represents. */
|
|
44
|
+
Slot: FormPanelSlot;
|
|
45
|
+
/** The record being edited — threaded through to every mounted panel. */
|
|
46
|
+
Record: BaseEntity;
|
|
47
|
+
/** The host form component — used by panels for EditMode + dirty notifications. */
|
|
48
|
+
FormComponent: BaseFormComponent;
|
|
49
|
+
/** Optional form context — same shape collapsible-panel chrome expects. */
|
|
50
|
+
FormContext?: FormContext;
|
|
51
|
+
private anchor;
|
|
52
|
+
private mounted;
|
|
53
|
+
private readonly destroy$;
|
|
54
|
+
private registeredSlot;
|
|
55
|
+
/** Tracks synchronous re-entry depth into remount() so a future refactor
|
|
56
|
+
* that reintroduces a remount loop surfaces loudly instead of freezing. */
|
|
57
|
+
private remountDepth;
|
|
58
|
+
constructor(coordinator?: FormSlotCoordinator | undefined);
|
|
59
|
+
ngOnInit(): void;
|
|
60
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
61
|
+
ngOnDestroy(): void;
|
|
62
|
+
private remount;
|
|
63
|
+
/** Find registrations whose metadata declares this exact entity + slot. */
|
|
64
|
+
private findRegistrations;
|
|
65
|
+
/**
|
|
66
|
+
* Find panels registered for a different slot that should mount HERE
|
|
67
|
+
* because their preferred slot isn't present and this slot is the next
|
|
68
|
+
* existing one in the fallback chain.
|
|
69
|
+
*
|
|
70
|
+
* Only meaningful when we have a coordinator (i.e., we're inside an
|
|
71
|
+
* `<mj-record-form-container>`).
|
|
72
|
+
*/
|
|
73
|
+
private findOrphans;
|
|
74
|
+
private unmountAll;
|
|
75
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FormPanelSlotComponent, [{ optional: true; }]>;
|
|
76
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FormPanelSlotComponent, "mj-form-panel-slot", never, { "Entity": { "alias": "Entity"; "required": false; }; "Slot": { "alias": "Slot"; "required": false; }; "Record": { "alias": "Record"; "required": false; }; "FormComponent": { "alias": "FormComponent"; "required": false; }; "FormContext": { "alias": "FormContext"; "required": false; }; }, {}, never, never, false, never>;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=form-panel-slot.component.d.ts.map
|
|
@@ -0,0 +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,EAIhB,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;;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAWa,sBAAuB,YAAW,MAAM,EAAE,SAAS,EAAE,SAAS;IAsB/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IArBrD,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;gFAC4E;IAC5E,OAAO,CAAC,YAAY,CAAK;gBAEgB,WAAW,CAAC,EAAE,mBAAmB,YAAA;IAE1E,QAAQ,IAAI,IAAI;IAahB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IA4BzC,WAAW,IAAI,IAAI;IAUnB,OAAO,CAAC,OAAO;IAqDf,2EAA2E;IAC3E,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,UAAU;yCAxKT,sBAAsB;2CAAtB,sBAAsB;CA4KlC"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Component, Input, Optional, ViewChild, ViewContainerRef, } from '@angular/core';
|
|
2
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
3
|
+
import { LogError } from '@memberjunction/core';
|
|
4
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
5
|
+
import { BaseFormPanel } from './base-form-panel';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
import * as i1 from "./form-slot-coordinator.service";
|
|
8
|
+
const _c0 = ["anchor"];
|
|
9
|
+
/**
|
|
10
|
+
* `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every
|
|
11
|
+
* registered `BaseFormPanel` whose metadata matches the slot's `Entity` +
|
|
12
|
+
* `Slot` inputs. Generated entity-form HTML emits one of these at each
|
|
13
|
+
* well-known slot position so consumers can extend the form WITHOUT replacing
|
|
14
|
+
* it via the custom-form override pattern.
|
|
15
|
+
*
|
|
16
|
+
* **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`
|
|
17
|
+
* to find every panel registered with metadata `{ entity, slot }` matching this
|
|
18
|
+
* slot. Registrations without a metadata bag (or without these required keys)
|
|
19
|
+
* are silently ignored so legacy registrations don't accidentally bind.
|
|
20
|
+
*
|
|
21
|
+
* **Ordering**: registrations are sorted by `metadata.sortKey` descending —
|
|
22
|
+
* higher number renders first within the slot. Use ranges (100/50/10) so future
|
|
23
|
+
* panels can wedge in without renumbering. Ties fall back to the
|
|
24
|
+
* `ClassRegistration.Priority` field (also descending), and finally to
|
|
25
|
+
* registration order.
|
|
26
|
+
*
|
|
27
|
+
* **Fallback chain** (see `FormSlotCoordinator`): each slot host registers
|
|
28
|
+
* with the per-form coordinator and additionally renders panels whose
|
|
29
|
+
* REGISTERED slot is missing in the current form, when this host happens to
|
|
30
|
+
* be the next-existing slot down the chain. The container template guarantees
|
|
31
|
+
* an `after-everything` slot host, so fallback always terminates somewhere.
|
|
32
|
+
* Downstream consumers running an older form template (no CodeGen rerun
|
|
33
|
+
* yet) get every panel rendered at the bottom; consumers on the new template
|
|
34
|
+
* get them in the preferred position.
|
|
35
|
+
*
|
|
36
|
+
* **Re-mount**: any input change OR a coordinator change (another slot
|
|
37
|
+
* registers/deregisters) triggers a re-render so fallback assignments stay
|
|
38
|
+
* correct as slots come and go.
|
|
39
|
+
*/
|
|
40
|
+
export class FormPanelSlotComponent {
|
|
41
|
+
coordinator;
|
|
42
|
+
/** The entity name (e.g., `"MJ: Content Sources"`) being edited. */
|
|
43
|
+
Entity;
|
|
44
|
+
/** Which slot position this host represents. */
|
|
45
|
+
Slot;
|
|
46
|
+
/** The record being edited — threaded through to every mounted panel. */
|
|
47
|
+
Record;
|
|
48
|
+
/** The host form component — used by panels for EditMode + dirty notifications. */
|
|
49
|
+
FormComponent;
|
|
50
|
+
/** Optional form context — same shape collapsible-panel chrome expects. */
|
|
51
|
+
FormContext;
|
|
52
|
+
anchor;
|
|
53
|
+
mounted = [];
|
|
54
|
+
destroy$ = new Subject();
|
|
55
|
+
registeredSlot = null;
|
|
56
|
+
/** Tracks synchronous re-entry depth into remount() so a future refactor
|
|
57
|
+
* that reintroduces a remount loop surfaces loudly instead of freezing. */
|
|
58
|
+
remountDepth = 0;
|
|
59
|
+
constructor(coordinator) {
|
|
60
|
+
this.coordinator = coordinator;
|
|
61
|
+
}
|
|
62
|
+
ngOnInit() {
|
|
63
|
+
if (this.Slot && this.coordinator) {
|
|
64
|
+
this.coordinator.registerSlot(this.Slot);
|
|
65
|
+
this.registeredSlot = this.Slot;
|
|
66
|
+
// Re-mount whenever the coordinator's known-slot set changes —
|
|
67
|
+
// a panel that was previously orphaned to us might now have a
|
|
68
|
+
// better slot available (or vice versa).
|
|
69
|
+
this.coordinator.changes
|
|
70
|
+
.pipe(takeUntil(this.destroy$))
|
|
71
|
+
.subscribe(() => this.remount());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
ngOnChanges(changes) {
|
|
75
|
+
if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {
|
|
76
|
+
// Inputs still being wired — wait for the next pass.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Only remount when something that affects WHICH panels to mount has
|
|
80
|
+
// changed. FormContext only flows through to mounted panels — pushing
|
|
81
|
+
// it to the existing panels avoids an unmount/remount cycle.
|
|
82
|
+
//
|
|
83
|
+
// Critical for performance: the host form often reconstructs
|
|
84
|
+
// formContext on every CD pass. Treating that as a structural change
|
|
85
|
+
// would loop (remount → detectChanges → another CD → new FormContext
|
|
86
|
+
// → remount again).
|
|
87
|
+
const meaningfulKeys = ['Entity', 'Slot', 'Record', 'FormComponent'];
|
|
88
|
+
const meaningfulChange = meaningfulKeys.some(k => changes[k]?.currentValue !== changes[k]?.previousValue);
|
|
89
|
+
if (!meaningfulChange && changes['FormContext'] && this.mounted.length > 0) {
|
|
90
|
+
for (const ref of this.mounted) {
|
|
91
|
+
ref.instance.FormContext = this.FormContext;
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!meaningfulChange && this.mounted.length > 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.remount();
|
|
99
|
+
}
|
|
100
|
+
ngOnDestroy() {
|
|
101
|
+
this.destroy$.next();
|
|
102
|
+
this.destroy$.complete();
|
|
103
|
+
if (this.coordinator && this.registeredSlot) {
|
|
104
|
+
this.coordinator.deregisterSlot(this.registeredSlot);
|
|
105
|
+
this.registeredSlot = null;
|
|
106
|
+
}
|
|
107
|
+
this.unmountAll();
|
|
108
|
+
}
|
|
109
|
+
remount() {
|
|
110
|
+
this.remountDepth++;
|
|
111
|
+
if (this.remountDepth > 5) {
|
|
112
|
+
console.error(`[mj-form-panel-slot] LOOP DETECTED — remount depth ${this.remountDepth} on slot=${this.Slot}. Bailing.`);
|
|
113
|
+
this.remountDepth--;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.unmountAll();
|
|
117
|
+
// 1. Direct matches: panels registered for THIS slot.
|
|
118
|
+
const direct = this.findRegistrations(this.Slot);
|
|
119
|
+
// 2. Orphans: panels registered for some OTHER slot whose preferred
|
|
120
|
+
// slot doesn't exist in this form, AND this slot host is the
|
|
121
|
+
// coordinator-resolved fallback for them. Without a coordinator
|
|
122
|
+
// (no parent container), no orphan handling — panels just bind
|
|
123
|
+
// to their literal slot.
|
|
124
|
+
const orphans = this.findOrphans();
|
|
125
|
+
const all = [...direct, ...orphans];
|
|
126
|
+
if (all.length === 0) {
|
|
127
|
+
this.remountDepth--;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Sort: higher sortKey first, then higher Priority, then registration order.
|
|
131
|
+
all.sort((a, b) => {
|
|
132
|
+
const aSort = a.Metadata?.sortKey ?? 0;
|
|
133
|
+
const bSort = b.Metadata?.sortKey ?? 0;
|
|
134
|
+
if (aSort !== bSort)
|
|
135
|
+
return bSort - aSort;
|
|
136
|
+
return b.Priority - a.Priority;
|
|
137
|
+
});
|
|
138
|
+
for (const reg of all) {
|
|
139
|
+
try {
|
|
140
|
+
const ctor = reg.SubClass;
|
|
141
|
+
const ref = this.anchor.createComponent(ctor);
|
|
142
|
+
ref.instance.Record = this.Record;
|
|
143
|
+
ref.instance.FormComponent = this.FormComponent;
|
|
144
|
+
if (this.FormContext)
|
|
145
|
+
ref.instance.FormContext = this.FormContext;
|
|
146
|
+
// No detectChanges() — Angular's normal CD pass picks the new
|
|
147
|
+
// component up. Calling detectChanges() synchronously inside
|
|
148
|
+
// an ongoing CD cycle (which is when ngOnChanges → remount
|
|
149
|
+
// fires) can re-enter and cause an infinite loop if anything
|
|
150
|
+
// upstream is reconstructing inputs on each CD pass.
|
|
151
|
+
this.mounted.push(ref);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
LogError(`[mj-form-panel-slot] Failed to mount panel for ${this.Entity}:${this.Slot}: ${e instanceof Error ? e.message : String(e)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.remountDepth--;
|
|
158
|
+
}
|
|
159
|
+
/** Find registrations whose metadata declares this exact entity + slot. */
|
|
160
|
+
findRegistrations(slot) {
|
|
161
|
+
return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, (metadata) => {
|
|
162
|
+
if (!metadata)
|
|
163
|
+
return false;
|
|
164
|
+
const meta = metadata;
|
|
165
|
+
return meta.entity === this.Entity && meta.slot === slot;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Find panels registered for a different slot that should mount HERE
|
|
170
|
+
* because their preferred slot isn't present and this slot is the next
|
|
171
|
+
* existing one in the fallback chain.
|
|
172
|
+
*
|
|
173
|
+
* Only meaningful when we have a coordinator (i.e., we're inside an
|
|
174
|
+
* `<mj-record-form-container>`).
|
|
175
|
+
*/
|
|
176
|
+
findOrphans() {
|
|
177
|
+
if (!this.coordinator)
|
|
178
|
+
return [];
|
|
179
|
+
const allForEntity = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, (metadata) => {
|
|
180
|
+
if (!metadata)
|
|
181
|
+
return false;
|
|
182
|
+
const meta = metadata;
|
|
183
|
+
return meta.entity === this.Entity && meta.slot != null && meta.slot !== this.Slot;
|
|
184
|
+
});
|
|
185
|
+
return allForEntity.filter(reg => {
|
|
186
|
+
const meta = reg.Metadata;
|
|
187
|
+
if (!meta?.slot)
|
|
188
|
+
return false;
|
|
189
|
+
const resolved = this.coordinator.resolveSlot(meta.slot);
|
|
190
|
+
return resolved === this.Slot;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
unmountAll() {
|
|
194
|
+
this.anchor.clear();
|
|
195
|
+
this.mounted = [];
|
|
196
|
+
}
|
|
197
|
+
static ɵfac = function FormPanelSlotComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormPanelSlotComponent)(i0.ɵɵdirectiveInject(i1.FormSlotCoordinator, 8)); };
|
|
198
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FormPanelSlotComponent, selectors: [["mj-form-panel-slot"]], viewQuery: function FormPanelSlotComponent_Query(rf, ctx) { if (rf & 1) {
|
|
199
|
+
i0.ɵɵviewQuery(_c0, 7, ViewContainerRef);
|
|
200
|
+
} if (rf & 2) {
|
|
201
|
+
let _t;
|
|
202
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.anchor = _t.first);
|
|
203
|
+
} }, inputs: { Entity: "Entity", Slot: "Slot", Record: "Record", FormComponent: "FormComponent", FormContext: "FormContext" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 2, vars: 0, consts: [["anchor", ""]], template: function FormPanelSlotComponent_Template(rf, ctx) { if (rf & 1) {
|
|
204
|
+
i0.ɵɵelementContainer(0, null, 0);
|
|
205
|
+
} }, styles: ["[_nghost-%COMP%] { display: contents; }"] });
|
|
206
|
+
}
|
|
207
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormPanelSlotComponent, [{
|
|
208
|
+
type: Component,
|
|
209
|
+
args: [{ standalone: false, selector: 'mj-form-panel-slot', template: `<ng-container #anchor></ng-container>`, styles: [":host { display: contents; }"] }]
|
|
210
|
+
}], () => [{ type: i1.FormSlotCoordinator, decorators: [{
|
|
211
|
+
type: Optional
|
|
212
|
+
}] }], { Entity: [{
|
|
213
|
+
type: Input
|
|
214
|
+
}], Slot: [{
|
|
215
|
+
type: Input
|
|
216
|
+
}], Record: [{
|
|
217
|
+
type: Input
|
|
218
|
+
}], FormComponent: [{
|
|
219
|
+
type: Input
|
|
220
|
+
}], FormContext: [{
|
|
221
|
+
type: Input
|
|
222
|
+
}], anchor: [{
|
|
223
|
+
type: ViewChild,
|
|
224
|
+
args: ['anchor', { read: ViewContainerRef, static: true }]
|
|
225
|
+
}] }); })();
|
|
226
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormPanelSlotComponent, { className: "FormPanelSlotComponent", filePath: "src/lib/panel-slot/form-panel-slot.component.ts", lineNumber: 64 }); })();
|
|
227
|
+
//# sourceMappingURL=form-panel-slot.component.js.map
|
|
@@ -0,0 +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 /** Find registrations whose metadata declares this exact entity + slot. */\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 meta.entity === this.Entity && meta.slot === slot;\n },\n );\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 meta.entity === this.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,57 @@
|
|
|
1
|
+
import { FormPanelSlot } from './base-form-panel';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Document-order chain used by the slot host to figure out where to render a
|
|
5
|
+
* panel whose preferred slot is missing in the current form. Walks forward:
|
|
6
|
+
* a panel registered for `after-fields` looks for the next existing slot down
|
|
7
|
+
* the chain (`after-related`, then `after-everything`) until it finds one.
|
|
8
|
+
*
|
|
9
|
+
* The chain is intentionally short — these are the only positions any panel
|
|
10
|
+
* should ever need. New positions can be added by inserting into this array
|
|
11
|
+
* at the right document-order position.
|
|
12
|
+
*/
|
|
13
|
+
export declare const FORM_SLOT_CHAIN: FormPanelSlot[];
|
|
14
|
+
/**
|
|
15
|
+
* Per-form coordinator that tracks which slot positions are physically present
|
|
16
|
+
* in the current form's DOM. Provided at the `<mj-record-form-container>` level
|
|
17
|
+
* (one instance per form) so descendant slot hosts can register themselves and
|
|
18
|
+
* compute the fallback chain.
|
|
19
|
+
*
|
|
20
|
+
* The container template guarantees an `after-everything` slot host at the very
|
|
21
|
+
* bottom of the form body — so panels whose preferred slot is missing always
|
|
22
|
+
* have a final landing spot. Pre-CodeGen-regen forms still display every
|
|
23
|
+
* panel; post-regen forms display them at the better position.
|
|
24
|
+
*/
|
|
25
|
+
export declare class FormSlotCoordinator {
|
|
26
|
+
private readonly presentSlots;
|
|
27
|
+
private readonly changes$;
|
|
28
|
+
/** Tracks synchronous re-entry depth into safeEmit so an emit storm
|
|
29
|
+
* caused by a future refactor surfaces loudly instead of freezing. */
|
|
30
|
+
private emitDepth;
|
|
31
|
+
/** Slot host calls this on init. Idempotent — safe if invoked twice. */
|
|
32
|
+
registerSlot(slot: FormPanelSlot): void;
|
|
33
|
+
/** Slot host calls this on destroy. */
|
|
34
|
+
deregisterSlot(slot: FormPanelSlot): void;
|
|
35
|
+
hasSlot(slot: FormPanelSlot): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a preferred slot to the slot that should ACTUALLY render the
|
|
38
|
+
* panel. Returns the preferred slot if it's present; otherwise walks
|
|
39
|
+
* forward in the chain to find the next existing slot.
|
|
40
|
+
*
|
|
41
|
+
* Returns null only in the (impossible) case where no slot at all is
|
|
42
|
+
* present — the container guarantees `after-everything` exists, so this
|
|
43
|
+
* should never happen in practice.
|
|
44
|
+
*/
|
|
45
|
+
resolveSlot(preferred: FormPanelSlot): FormPanelSlot | null;
|
|
46
|
+
/** RxJS stream that fires whenever a slot registers or deregisters. */
|
|
47
|
+
get changes(): import("rxjs").Observable<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Emit with re-entrancy detection. If we're already in the middle of an
|
|
50
|
+
* emit (i.e., a subscriber triggered another emit), log loudly so the loop
|
|
51
|
+
* is visible in the console instead of silently freezing the browser.
|
|
52
|
+
*/
|
|
53
|
+
private safeEmit;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FormSlotCoordinator, never>;
|
|
55
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<FormSlotCoordinator>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=form-slot-coordinator.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-slot-coordinator.service.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-slot-coordinator.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;;AAElD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EAM1C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBACa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD;2EACuE;IACvE,OAAO,CAAC,SAAS,CAAK;IAEtB,wEAAwE;IACjE,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAO9C,uCAAuC;IAChC,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAOzC,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO;IAI5C;;;;;;;;OAQG;IACI,WAAW,CAAC,SAAS,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;IAYlE,uEAAuE;IACvE,IAAW,OAAO,oCAEjB;IAED;;;;OAIG;IACH,OAAO,CAAC,QAAQ;yCA1DP,mBAAmB;6CAAnB,mBAAmB;CAsE/B"}
|