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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +4 -1
  3. package/dist/lib/base-form-component.d.ts +93 -3
  4. package/dist/lib/base-form-component.d.ts.map +1 -1
  5. package/dist/lib/base-form-component.js +240 -9
  6. package/dist/lib/base-form-component.js.map +1 -1
  7. package/dist/lib/chrome/base-form-policy.d.ts +26 -0
  8. package/dist/lib/chrome/base-form-policy.d.ts.map +1 -0
  9. package/dist/lib/chrome/base-form-policy.js +9 -0
  10. package/dist/lib/chrome/base-form-policy.js.map +1 -0
  11. package/dist/lib/chrome/clipped-title.d.ts +8 -0
  12. package/dist/lib/chrome/clipped-title.d.ts.map +1 -0
  13. package/dist/lib/chrome/clipped-title.js +18 -0
  14. package/dist/lib/chrome/clipped-title.js.map +1 -0
  15. package/dist/lib/chrome/form-chrome-coordinator.service.d.ts +35 -0
  16. package/dist/lib/chrome/form-chrome-coordinator.service.d.ts.map +1 -0
  17. package/dist/lib/chrome/form-chrome-coordinator.service.js +96 -0
  18. package/dist/lib/chrome/form-chrome-coordinator.service.js.map +1 -0
  19. package/dist/lib/chrome/form-chrome-rail-pref.d.ts +24 -0
  20. package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -0
  21. package/dist/lib/chrome/form-chrome-rail-pref.js +52 -0
  22. package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -0
  23. package/dist/lib/chrome/form-chrome.d.ts +59 -0
  24. package/dist/lib/chrome/form-chrome.d.ts.map +1 -0
  25. package/dist/lib/chrome/form-chrome.js +37 -0
  26. package/dist/lib/chrome/form-chrome.js.map +1 -0
  27. package/dist/lib/chrome/load-form-chrome-rules.d.ts +8 -0
  28. package/dist/lib/chrome/load-form-chrome-rules.d.ts.map +1 -0
  29. package/dist/lib/chrome/load-form-chrome-rules.js +70 -0
  30. package/dist/lib/chrome/load-form-chrome-rules.js.map +1 -0
  31. package/dist/lib/chrome/resolve-form-chrome.d.ts +114 -0
  32. package/dist/lib/chrome/resolve-form-chrome.d.ts.map +1 -0
  33. package/dist/lib/chrome/resolve-form-chrome.js +713 -0
  34. package/dist/lib/chrome/resolve-form-chrome.js.map +1 -0
  35. package/dist/lib/container/record-form-container.component.d.ts +170 -6
  36. package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
  37. package/dist/lib/container/record-form-container.component.js +1194 -62
  38. package/dist/lib/container/record-form-container.component.js.map +1 -1
  39. package/dist/lib/explorer-entity-data-grid.component.d.ts +54 -1
  40. package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
  41. package/dist/lib/explorer-entity-data-grid.component.js +143 -7
  42. package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
  43. package/dist/lib/form-record-refresh.coordinator.d.ts +27 -0
  44. package/dist/lib/form-record-refresh.coordinator.d.ts.map +1 -0
  45. package/dist/lib/form-record-refresh.coordinator.js +33 -0
  46. package/dist/lib/form-record-refresh.coordinator.js.map +1 -0
  47. package/dist/lib/form-state.interface.d.ts +4 -0
  48. package/dist/lib/form-state.interface.d.ts.map +1 -1
  49. package/dist/lib/form-state.interface.js.map +1 -1
  50. package/dist/lib/form-state.service.d.ts +5 -0
  51. package/dist/lib/form-state.service.d.ts.map +1 -1
  52. package/dist/lib/form-state.service.js +23 -2
  53. package/dist/lib/form-state.service.js.map +1 -1
  54. package/dist/lib/host/entity-form-host.component.d.ts +5 -1
  55. package/dist/lib/host/entity-form-host.component.d.ts.map +1 -1
  56. package/dist/lib/host/entity-form-host.component.js +16 -2
  57. package/dist/lib/host/entity-form-host.component.js.map +1 -1
  58. package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts +6 -2
  59. package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts.map +1 -1
  60. package/dist/lib/isa-related-panel/isa-related-panel.component.js +15 -1
  61. package/dist/lib/isa-related-panel/isa-related-panel.component.js.map +1 -1
  62. package/dist/lib/panel/collapsible-panel.component.d.ts +24 -2
  63. package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
  64. package/dist/lib/panel/collapsible-panel.component.js +104 -10
  65. package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
  66. package/dist/lib/panel-slot/base-form-panel.d.ts +85 -1
  67. package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
  68. package/dist/lib/panel-slot/base-form-panel.js +72 -3
  69. package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
  70. package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts +8 -0
  71. package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts.map +1 -0
  72. package/dist/lib/panel-slot/collect-form-panel-registrations.js +19 -0
  73. package/dist/lib/panel-slot/collect-form-panel-registrations.js.map +1 -0
  74. package/dist/lib/panel-slot/form-contribution.d.ts +79 -0
  75. package/dist/lib/panel-slot/form-contribution.d.ts.map +1 -0
  76. package/dist/lib/panel-slot/form-contribution.js +253 -0
  77. package/dist/lib/panel-slot/form-contribution.js.map +1 -0
  78. package/dist/lib/panel-slot/form-contributions.component.d.ts +27 -0
  79. package/dist/lib/panel-slot/form-contributions.component.d.ts.map +1 -0
  80. package/dist/lib/panel-slot/form-contributions.component.js +83 -0
  81. package/dist/lib/panel-slot/form-contributions.component.js.map +1 -0
  82. package/dist/lib/panel-slot/form-panel-slot.component.d.ts +2 -0
  83. package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -1
  84. package/dist/lib/panel-slot/form-panel-slot.component.js +35 -4
  85. package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -1
  86. package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts +22 -0
  87. package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts.map +1 -0
  88. package/dist/lib/panel-slot/related-entity-grid-panel.component.js +78 -0
  89. package/dist/lib/panel-slot/related-entity-grid-panel.component.js.map +1 -0
  90. package/dist/lib/related-grid-height.d.ts +20 -0
  91. package/dist/lib/related-grid-height.d.ts.map +1 -0
  92. package/dist/lib/related-grid-height.js +30 -0
  93. package/dist/lib/related-grid-height.js.map +1 -0
  94. package/dist/lib/section-manager/section-manager.component.d.ts +20 -1
  95. package/dist/lib/section-manager/section-manager.component.d.ts.map +1 -1
  96. package/dist/lib/section-manager/section-manager.component.js +204 -25
  97. package/dist/lib/section-manager/section-manager.component.js.map +1 -1
  98. package/dist/lib/toolbar/form-toolbar.component.d.ts +55 -4
  99. package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
  100. package/dist/lib/toolbar/form-toolbar.component.js +734 -306
  101. package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
  102. package/dist/lib/types/form-events.d.ts +35 -0
  103. package/dist/lib/types/form-events.d.ts.map +1 -1
  104. package/dist/lib/types/form-events.js +45 -0
  105. package/dist/lib/types/form-events.js.map +1 -1
  106. package/dist/lib/types/form-toolbar-item.d.ts +145 -0
  107. package/dist/lib/types/form-toolbar-item.d.ts.map +1 -0
  108. package/dist/lib/types/form-toolbar-item.js +2 -0
  109. package/dist/lib/types/form-toolbar-item.js.map +1 -0
  110. package/dist/lib/types/form-types.d.ts +8 -0
  111. package/dist/lib/types/form-types.d.ts.map +1 -1
  112. package/dist/lib/types/form-types.js.map +1 -1
  113. package/dist/lib/types/toolbar-config.d.ts +7 -2
  114. package/dist/lib/types/toolbar-config.d.ts.map +1 -1
  115. package/dist/lib/types/toolbar-config.js +6 -0
  116. package/dist/lib/types/toolbar-config.js.map +1 -1
  117. package/dist/module.d.ts +17 -14
  118. package/dist/module.d.ts.map +1 -1
  119. package/dist/module.js +14 -0
  120. package/dist/module.js.map +1 -1
  121. package/dist/public-api.d.ts +13 -0
  122. package/dist/public-api.d.ts.map +1 -1
  123. package/dist/public-api.js +14 -0
  124. package/dist/public-api.js.map +1 -1
  125. package/package.json +19 -18
@@ -1,4 +1,4 @@
1
- import { Directive, Input } from '@angular/core';
1
+ import { Directive, HostBinding, Input } from '@angular/core';
2
2
  import { ValidationResult } from '@memberjunction/core';
3
3
  import * as i0 from "@angular/core";
4
4
  /**
@@ -20,6 +20,12 @@ import * as i0 from "@angular/core";
20
20
  * record itself does can leave this method off.
21
21
  */
22
22
  export class BaseFormPanel {
23
+ /**
24
+ * Slot-mounted hosts must not sit in the left-nav flex column — leftover
25
+ * height targets `mj-collapsible-panel` as a direct child of
26
+ * `.mj-forms-all-panels`. `display: contents` makes the inner panel that child.
27
+ */
28
+ HostDisplay = 'contents';
23
29
  /** The entity record being edited. Set by the slot host before view init. */
24
30
  Record;
25
31
  /** The host form component (use for EditMode, dirty notifications, etc). Set by the slot host. */
@@ -49,12 +55,75 @@ export class BaseFormPanel {
49
55
  result.Success = true;
50
56
  return result;
51
57
  }
58
+ /**
59
+ * Called after the parent form reloads the current record from the database.
60
+ * Default is a no-op. Override to refresh panel-owned data that is not a
61
+ * related-entity grid (those already subscribe to
62
+ * {@link FormRecordRefreshCoordinator} themselves).
63
+ */
64
+ OnRecordRefreshed(_record) {
65
+ // Subclasses can override
66
+ }
67
+ /**
68
+ * Registers a dynamic toolbar action item / button into the host form toolbar.
69
+ */
70
+ RegisterToolbarItem(item) {
71
+ this.FormComponent?.RegisterToolbarItem(item);
72
+ }
73
+ /**
74
+ * Unregisters a previously registered dynamic toolbar item by key.
75
+ */
76
+ UnregisterToolbarItem(key) {
77
+ this.FormComponent?.UnregisterToolbarItem(key);
78
+ }
79
+ /**
80
+ * Configures overrides for any toolbar item (standard built-in items like 'edit',
81
+ * 'delete', 'favorite', 'history', etc., or custom items).
82
+ */
83
+ ConfigureToolbarItem(key, overrides) {
84
+ this.FormComponent?.ConfigureToolbarItem(key, overrides);
85
+ }
86
+ /**
87
+ * Dynamically hides a toolbar item by key.
88
+ */
89
+ HideToolbarItem(key) {
90
+ this.FormComponent?.HideToolbarItem(key);
91
+ }
92
+ /**
93
+ * Dynamically shows a toolbar item by key.
94
+ */
95
+ ShowToolbarItem(key) {
96
+ this.FormComponent?.ShowToolbarItem(key);
97
+ }
98
+ /**
99
+ * Dynamically disables a toolbar item by key, with an optional reason string for the tooltip.
100
+ */
101
+ DisableToolbarItem(key, reason) {
102
+ this.FormComponent?.DisableToolbarItem(key, reason);
103
+ }
104
+ /**
105
+ * Dynamically enables a toolbar item by key.
106
+ */
107
+ EnableToolbarItem(key) {
108
+ this.FormComponent?.EnableToolbarItem(key);
109
+ }
110
+ /**
111
+ * Sets the numeric display order for a toolbar item.
112
+ */
113
+ SetToolbarItemOrder(key, order) {
114
+ this.FormComponent?.SetToolbarItemOrder(key, order);
115
+ }
52
116
  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" } });
117
+ static ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseFormPanel, hostVars: 2, hostBindings: function BaseFormPanel_HostBindings(rf, ctx) { if (rf & 2) {
118
+ i0.ɵɵstyleProp("display", ctx.HostDisplay);
119
+ } }, inputs: { Record: "Record", FormComponent: "FormComponent", FormContext: "FormContext" } });
54
120
  }
55
121
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseFormPanel, [{
56
122
  type: Directive
57
- }], null, { Record: [{
123
+ }], null, { HostDisplay: [{
124
+ type: HostBinding,
125
+ args: ['style.display']
126
+ }], Record: [{
58
127
  type: Input
59
128
  }], FormComponent: [{
60
129
  type: Input
@@ -1 +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;;AA+DpE;;;;;;;;;;;;;;;;;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 exact entity name (case-sensitive equality enforced by the\n * slot host), OR the wildcard `'*'` to register an\n * **entity-agnostic** panel that mounts on EVERY entity's form.\n * Wildcard panels are expected to self-hide (render nothing) when\n * they don't apply to the current record, so the cross-cutting\n * registration stays unobtrusive on forms it has no business on.\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"]}
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,WAAW,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAc,gBAAgB,EAAsB,MAAM,sBAAsB,CAAC;;AAqGxF;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,OAAgB,aAAa;IAC/B;;;;OAIG;IAEM,WAAW,GAAG,UAAU,CAAC;IAElC,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;IAED;;;;;OAKG;IACI,iBAAiB,CAAC,OAAgB;QACrC,0BAA0B;IAC9B,CAAC;IAED;;OAEG;IACI,mBAAmB,CAAC,IAA2B;QAClD,IAAI,CAAC,aAAa,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACI,qBAAqB,CAAC,GAAW;QACpC,IAAI,CAAC,aAAa,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC;IAED;;;OAGG;IACI,oBAAoB,CAAC,GAAuB,EAAE,SAAyC;QAC1F,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC;IAED;;OAEG;IACI,eAAe,CAAC,GAAuB;QAC1C,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACI,eAAe,CAAC,GAAuB;QAC1C,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,GAAuB,EAAE,MAAe;QAC9D,IAAI,CAAC,aAAa,EAAE,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACI,iBAAiB,CAAC,GAAuB;QAC5C,IAAI,CAAC,aAAa,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACI,mBAAmB,CAAC,GAAuB,EAAE,KAAa;QAC7D,IAAI,CAAC,aAAa,EAAE,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;uGA1GiB,aAAa;6DAAb,aAAa;YAAb,0CAAa;;;iFAAb,aAAa;cADlC,SAAS;;kBAOL,WAAW;mBAAC,eAAe;;kBAI3B,KAAK;;kBAEL,KAAK;;kBAEL,KAAK","sourcesContent":["import { Directive, HostBinding, Input } from '@angular/core';\nimport { BaseEntity, ValidationResult, type FormInclusion } from '@memberjunction/core';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { FormToolbarItemConfig, FormToolbarItemKey } from '../types/form-toolbar-item';\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 exact entity name (case-sensitive equality enforced by the\n * slot host), OR the wildcard `'*'` to register an\n * **entity-agnostic** panel that mounts on EVERY entity's form.\n * Wildcard panels are expected to self-hide (render nothing) when\n * they don't apply to the current record, so the cross-cutting\n * registration stays unobtrusive on forms it has no business on.\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 * Identity for last-wins collapse. Two registrations with the same key\n * compete; highest ClassFactory `Priority` wins. Omit to derive:\n * `related:${relatedEntity}:${relatedJoinField}` when claiming a\n * relationship, otherwise the registration is unique and never collapses.\n */\n contributionKey?: string;\n /**\n * When set, this panel **replaces** the default related-entity grid for\n * that relationship on `entity`'s form. The baked CodeGen panel (if any)\n * is hidden; the stock fill-in grid is not mounted.\n */\n relatedEntity?: string;\n /**\n * Disambiguates two FKs to the same related entity (BillTo vs ShipTo).\n * Compared after stripping wrapping `[]`.\n */\n relatedJoinField?: string;\n /**\n * CodeGen `SectionKey` of a baked field (or other) panel this contribution\n * replaces — e.g. `'details'`, `'personalIdentity'`. Hidden at runtime via\n * `HiddenSectionKeys`. Use with `slot: 'before-fields'` for a form hero that\n * is not a collapsible panel. Must name a concrete `entity` (not `'*'`).\n */\n replacesSectionKey?: string;\n /**\n * Pin this contribution to a chrome bucket instead of its own rail item.\n * `'details'` — leftover own-fields group. `'more'` — overflow folder.\n */\n chromeGroup?: 'details' | 'more';\n /**\n * L1 inclusion for this contribution (same verbs as relationships).\n * `'Primary'` — own first-class rail item, in the lead band before Details.\n * `'More'` — overflow folder. `'None'` — hidden.\n * L3 `MJ: Form Chrome Rules` still wins. `chromeGroup` still merges.\n */\n inclusion?: FormInclusion;\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 /**\n * Slot-mounted hosts must not sit in the left-nav flex column — leftover\n * height targets `mj-collapsible-panel` as a direct child of\n * `.mj-forms-all-panels`. `display: contents` makes the inner panel that child.\n */\n @HostBinding('style.display')\n readonly HostDisplay = 'contents';\n\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 /**\n * Called after the parent form reloads the current record from the database.\n * Default is a no-op. Override to refresh panel-owned data that is not a\n * related-entity grid (those already subscribe to\n * {@link FormRecordRefreshCoordinator} themselves).\n */\n public OnRecordRefreshed(_record: TRecord): void {\n // Subclasses can override\n }\n\n /**\n * Registers a dynamic toolbar action item / button into the host form toolbar.\n */\n public RegisterToolbarItem(item: FormToolbarItemConfig): void {\n this.FormComponent?.RegisterToolbarItem(item);\n }\n\n /**\n * Unregisters a previously registered dynamic toolbar item by key.\n */\n public UnregisterToolbarItem(key: string): void {\n this.FormComponent?.UnregisterToolbarItem(key);\n }\n\n /**\n * Configures overrides for any toolbar item (standard built-in items like 'edit',\n * 'delete', 'favorite', 'history', etc., or custom items).\n */\n public ConfigureToolbarItem(key: FormToolbarItemKey, overrides: Partial<FormToolbarItemConfig>): void {\n this.FormComponent?.ConfigureToolbarItem(key, overrides);\n }\n\n /**\n * Dynamically hides a toolbar item by key.\n */\n public HideToolbarItem(key: FormToolbarItemKey): void {\n this.FormComponent?.HideToolbarItem(key);\n }\n\n /**\n * Dynamically shows a toolbar item by key.\n */\n public ShowToolbarItem(key: FormToolbarItemKey): void {\n this.FormComponent?.ShowToolbarItem(key);\n }\n\n /**\n * Dynamically disables a toolbar item by key, with an optional reason string for the tooltip.\n */\n public DisableToolbarItem(key: FormToolbarItemKey, reason?: string): void {\n this.FormComponent?.DisableToolbarItem(key, reason);\n }\n\n /**\n * Dynamically enables a toolbar item by key.\n */\n public EnableToolbarItem(key: FormToolbarItemKey): void {\n this.FormComponent?.EnableToolbarItem(key);\n }\n\n /**\n * Sets the numeric display order for a toolbar item.\n */\n public SetToolbarItemOrder(key: FormToolbarItemKey, order: number): void {\n this.FormComponent?.SetToolbarItemOrder(key, order);\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { FormContributionRegistration } from './form-contribution';
2
+ /**
3
+ * Every BaseFormPanel registration that carries an `entity` metadata field.
4
+ * Used by the composer and by `BaseFormComponent.formContext` to hide baked
5
+ * related sections a panel has claimed.
6
+ */
7
+ export declare function CollectFormPanelRegistrations(): FormContributionRegistration[];
8
+ //# sourceMappingURL=collect-form-panel-registrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-form-panel-registrations.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/collect-form-panel-registrations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,4BAA4B,EAAE,CAY9E"}
@@ -0,0 +1,19 @@
1
+ import { MJGlobal } from '@memberjunction/global';
2
+ import { BaseFormPanel } from './base-form-panel';
3
+ /**
4
+ * Every BaseFormPanel registration that carries an `entity` metadata field.
5
+ * Used by the composer and by `BaseFormComponent.formContext` to hide baked
6
+ * related sections a panel has claimed.
7
+ */
8
+ export function CollectFormPanelRegistrations() {
9
+ return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, (metadata) => {
10
+ if (!metadata)
11
+ return false;
12
+ const entity = metadata.entity;
13
+ return typeof entity === 'string' && entity.length > 0;
14
+ }).map((reg) => ({
15
+ Priority: reg.Priority,
16
+ Metadata: reg.Metadata,
17
+ }));
18
+ }
19
+ //# sourceMappingURL=collect-form-panel-registrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-form-panel-registrations.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/collect-form-panel-registrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAiC,MAAM,mBAAmB,CAAC;AAGjF;;;;GAIG;AACH,MAAM,UAAU,6BAA6B;IACzC,OAAO,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,6BAA6B,CAC/D,aAAa,EACb,CAAC,QAAQ,EAAE,EAAE;QACT,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,MAAM,MAAM,GAAI,QAAmD,CAAC,MAAM,CAAC;QAC3E,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3D,CAAC,CACJ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACZ,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAyC;KAC1D,CAAC,CAAC,CAAC;AACR,CAAC","sourcesContent":["import { MJGlobal } from '@memberjunction/global';\nimport { BaseFormPanel, FormPanelRegistrationMetadata } from './base-form-panel';\nimport type { FormContributionRegistration } from './form-contribution';\n\n/**\n * Every BaseFormPanel registration that carries an `entity` metadata field.\n * Used by the composer and by `BaseFormComponent.formContext` to hide baked\n * related sections a panel has claimed.\n */\nexport function CollectFormPanelRegistrations(): FormContributionRegistration[] {\n return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const entity = (metadata as Partial<FormPanelRegistrationMetadata>).entity;\n return typeof entity === 'string' && entity.length > 0;\n },\n ).map((reg) => ({\n Priority: reg.Priority,\n Metadata: reg.Metadata as FormPanelRegistrationMetadata,\n }));\n}\n"]}
@@ -0,0 +1,79 @@
1
+ import { FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';
2
+ /** Minimum relationship shape the composer reads. Satisfied by EntityRelationshipInfo. */
3
+ export interface FormContributionRelationship {
4
+ RelatedEntity: string;
5
+ RelatedEntityID: string;
6
+ RelatedEntityJoinField: string;
7
+ DisplayInForm: boolean;
8
+ DisplayName?: string | null;
9
+ Sequence?: number | null;
10
+ }
11
+ export interface FormContributionRegistration {
12
+ Priority: number;
13
+ Metadata: FormPanelRegistrationMetadata;
14
+ }
15
+ export interface ResolveFormContributionsInput {
16
+ EntityName: string;
17
+ RelatedEntities: readonly FormContributionRelationship[];
18
+ /** IS-A child entity IDs — CodeGen skips these (shown in the ISA side panel). */
19
+ IsaChildEntityIDs: readonly string[];
20
+ Registrations: readonly FormContributionRegistration[];
21
+ /** SectionKeys already projected as Variant="related-entity" in the form template. */
22
+ BakedSectionKeys: readonly string[];
23
+ ShowRelatedEntities: boolean;
24
+ }
25
+ export type FormContributionKind = 'registered' | 'stock-grid';
26
+ export interface FormContributionWinner {
27
+ ContributionKey: string;
28
+ Slot: FormPanelSlot;
29
+ SortKey: number;
30
+ Priority: number;
31
+ Kind: FormContributionKind;
32
+ RelatedEntity?: string;
33
+ RelatedJoinField?: string;
34
+ /** Field/other section this registered winner asked to hide. */
35
+ ReplacesSectionKey?: string;
36
+ BakedSectionKey: string;
37
+ DisplayName: string;
38
+ }
39
+ export interface ResolveFormContributionsResult {
40
+ Winners: FormContributionWinner[];
41
+ /** CodeGen section keys to hide because a registered panel claimed them. */
42
+ HiddenBakedSectionKeys: string[];
43
+ /** Stock grids the host should mount (unclaimed and not already baked). */
44
+ StockGrids: FormContributionWinner[];
45
+ }
46
+ /** Strip wrapping [] from a join field, matching CodeGen's tab-name helper. */
47
+ export declare function StripJoinFieldBrackets(joinField: string | null | undefined): string;
48
+ /**
49
+ * camelCase + identifier sanitize. Byte-compatible with
50
+ * `CodeGenLib` `angular-codegen.ts` `camelCase` — do not "improve".
51
+ */
52
+ export declare function FormSectionCamelCase(str: string): string;
53
+ export declare function RelatedContributionKey(relatedEntity: string, joinField?: string | null): string;
54
+ export declare function ResolveContributionKey(meta: FormPanelRegistrationMetadata): string;
55
+ /**
56
+ * SectionKey CodeGen emits for a related-entity panel. When more than one
57
+ * DisplayInForm relationship points at the same entity, the join field is
58
+ * appended so BillTo / ShipTo do not collide.
59
+ */
60
+ export declare function RelatedEntitySectionKey(relationship: FormContributionRelationship, displayInFormPeers: readonly FormContributionRelationship[]): string;
61
+ export declare function RelationshipDisplayName(relationship: FormContributionRelationship): string;
62
+ /**
63
+ * Last-wins collapse by contributionKey (or derived related key).
64
+ * Highest Priority keeps the slot. Registrations without a key never collapse.
65
+ */
66
+ export declare function CollapseFormPanelRegistrations<T extends {
67
+ Priority: number;
68
+ Metadata: FormPanelRegistrationMetadata;
69
+ }>(registrations: readonly T[]): T[];
70
+ /**
71
+ * Resolve what belongs on the form. Stock grids are only those the template
72
+ * did not already bake and that no panel claimed.
73
+ */
74
+ export declare function ResolveFormContributions(input: ResolveFormContributionsInput): ResolveFormContributionsResult;
75
+ /** Section keys a form should hide because a winning contribution claimed them. */
76
+ export declare function ContributionHiddenSectionKeys(entityName: string, relatedEntities: readonly FormContributionRelationship[], isaChildEntityIDs: readonly string[], registrations: readonly FormContributionRegistration[]): string[];
77
+ /** @deprecated Use {@link ContributionHiddenSectionKeys}. */
78
+ export declare function ClaimedRelatedSectionKeys(entityName: string, relatedEntities: readonly FormContributionRelationship[], isaChildEntityIDs: readonly string[], registrations: readonly FormContributionRegistration[]): string[];
79
+ //# sourceMappingURL=form-contribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-contribution.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-contribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Form contributions — pure resolve of what should appear on an entity form.
3
+ *
4
+ * A contribution is either a registered BaseFormPanel or the stock related-entity
5
+ * grid. Related claims replace the stock/baked grid. No Angular, no ClassFactory
6
+ * — the host queries registrations and feeds this.
7
+ *
8
+ * Section keys for related panels MUST match CodeGen's camelCase
9
+ * (`angular-codegen.ts` `camelCase` + related-entity sectionKey). If they drift,
10
+ * hide-baked and skip-baked miss and the user sees a double grid.
11
+ */
12
+ import { UUIDsEqual } from '@memberjunction/global';
13
+ /** Strip wrapping [] from a join field, matching CodeGen's tab-name helper. */
14
+ export function StripJoinFieldBrackets(joinField) {
15
+ return (joinField ?? '').trim().replace(/^\[/, '').replace(/\]$/, '');
16
+ }
17
+ /**
18
+ * camelCase + identifier sanitize. Byte-compatible with
19
+ * `CodeGenLib` `angular-codegen.ts` `camelCase` — do not "improve".
20
+ */
21
+ export function FormSectionCamelCase(str) {
22
+ const sanitized = str.replace(/[^a-zA-Z0-9\s]/g, ' ');
23
+ let result = sanitized
24
+ .replace(/\s(.)/g, (_match, char) => char.toUpperCase())
25
+ .replace(/\s/g, '')
26
+ .replace(/^(.)/, (_match, char) => char.toLowerCase());
27
+ if (/^\d/.test(result)) {
28
+ result = '_' + result;
29
+ }
30
+ return result.length === 0 ? 'section' : result;
31
+ }
32
+ export function RelatedContributionKey(relatedEntity, joinField) {
33
+ return `related:${relatedEntity.trim()}:${StripJoinFieldBrackets(joinField)}`;
34
+ }
35
+ export function ResolveContributionKey(meta) {
36
+ if (meta.contributionKey && meta.contributionKey.trim().length > 0) {
37
+ return meta.contributionKey.trim();
38
+ }
39
+ if (meta.relatedEntity && meta.relatedEntity.trim().length > 0) {
40
+ return RelatedContributionKey(meta.relatedEntity, meta.relatedJoinField);
41
+ }
42
+ return '';
43
+ }
44
+ /**
45
+ * SectionKey CodeGen emits for a related-entity panel. When more than one
46
+ * DisplayInForm relationship points at the same entity, the join field is
47
+ * appended so BillTo / ShipTo do not collide.
48
+ */
49
+ export function RelatedEntitySectionKey(relationship, displayInFormPeers) {
50
+ const sameEntity = displayInFormPeers.filter((peer) => UUIDsEqual(peer.RelatedEntityID, relationship.RelatedEntityID));
51
+ if (sameEntity.length > 1) {
52
+ return FormSectionCamelCase(`${relationship.RelatedEntity} ${StripJoinFieldBrackets(relationship.RelatedEntityJoinField)}`);
53
+ }
54
+ return FormSectionCamelCase(relationship.RelatedEntity);
55
+ }
56
+ export function RelationshipDisplayName(relationship) {
57
+ const named = relationship.DisplayName?.trim();
58
+ const raw = named && named.length > 0 ? named : relationship.RelatedEntity;
59
+ return raw.replace(/^[A-Za-z][A-Za-z0-9_]*:\s+/, '').trim() || raw;
60
+ }
61
+ function isIsaChild(relationship, isaChildIds) {
62
+ return isaChildIds.some((id) => UUIDsEqual(id, relationship.RelatedEntityID));
63
+ }
64
+ function visibleRelationships(related, isaChildIds) {
65
+ const visible = related.filter((rel) => rel.DisplayInForm && !isIsaChild(rel, isaChildIds));
66
+ return [...visible].sort((a, b) => {
67
+ const aSeq = a.Sequence ?? 999999;
68
+ const bSeq = b.Sequence ?? 999999;
69
+ if (aSeq !== bSeq)
70
+ return aSeq - bSeq;
71
+ return a.RelatedEntity.localeCompare(b.RelatedEntity);
72
+ });
73
+ }
74
+ function entityMatches(registeredEntity, formEntity) {
75
+ return registeredEntity === '*' || registeredEntity === formEntity;
76
+ }
77
+ function applicableRegistrations(entityName, registrations) {
78
+ return registrations.filter((reg) => {
79
+ const entity = reg.Metadata.entity;
80
+ if (!entity || !entityMatches(entity, entityName))
81
+ return false;
82
+ // A related claim on entity:'*' would hide that grid on every form.
83
+ // Claims must name the form entity.
84
+ // Related / field-section claims on entity:'*' would hide panels on every
85
+ // form. Those claims must name the form entity.
86
+ if ((reg.Metadata.relatedEntity || reg.Metadata.replacesSectionKey) && entity === '*') {
87
+ return false;
88
+ }
89
+ return true;
90
+ });
91
+ }
92
+ /**
93
+ * Last-wins collapse by contributionKey (or derived related key).
94
+ * Highest Priority keeps the slot. Registrations without a key never collapse.
95
+ */
96
+ export function CollapseFormPanelRegistrations(registrations) {
97
+ const winners = new Map();
98
+ let uniqueIndex = 0;
99
+ for (const reg of registrations) {
100
+ const key = ResolveContributionKey(reg.Metadata) || `__unique:${uniqueIndex++}`;
101
+ const incumbent = winners.get(key);
102
+ if (!incumbent || reg.Priority > incumbent.Priority) {
103
+ winners.set(key, reg);
104
+ }
105
+ }
106
+ return [...winners.values()];
107
+ }
108
+ function collapseRegistrations(registrations) {
109
+ const winners = new Map();
110
+ for (const reg of CollapseFormPanelRegistrations(registrations)) {
111
+ const key = ResolveContributionKey(reg.Metadata) || `${reg.Metadata.entity}:${reg.Metadata.slot}:${reg.Priority}`;
112
+ winners.set(key, reg);
113
+ }
114
+ return winners;
115
+ }
116
+ function stockWinner(relationship, peers) {
117
+ const join = StripJoinFieldBrackets(relationship.RelatedEntityJoinField);
118
+ return {
119
+ ContributionKey: RelatedContributionKey(relationship.RelatedEntity, join),
120
+ Slot: 'after-related',
121
+ SortKey: -(relationship.Sequence ?? 0),
122
+ Priority: 0,
123
+ Kind: 'stock-grid',
124
+ RelatedEntity: relationship.RelatedEntity,
125
+ RelatedJoinField: join,
126
+ BakedSectionKey: RelatedEntitySectionKey(relationship, peers),
127
+ DisplayName: RelationshipDisplayName(relationship),
128
+ };
129
+ }
130
+ function registeredWinner(key, reg, sectionKey, displayName) {
131
+ const meta = reg.Metadata;
132
+ return {
133
+ ContributionKey: key,
134
+ Slot: meta.slot,
135
+ SortKey: meta.sortKey ?? 0,
136
+ Priority: reg.Priority,
137
+ Kind: 'registered',
138
+ RelatedEntity: meta.relatedEntity,
139
+ RelatedJoinField: meta.relatedJoinField ? StripJoinFieldBrackets(meta.relatedJoinField) : undefined,
140
+ ReplacesSectionKey: meta.replacesSectionKey?.trim() || undefined,
141
+ BakedSectionKey: sectionKey,
142
+ DisplayName: displayName,
143
+ };
144
+ }
145
+ function sortWinners(winners) {
146
+ return [...winners].sort((a, b) => {
147
+ if (a.SortKey !== b.SortKey)
148
+ return b.SortKey - a.SortKey;
149
+ if (a.Priority !== b.Priority)
150
+ return b.Priority - a.Priority;
151
+ return a.ContributionKey.localeCompare(b.ContributionKey);
152
+ });
153
+ }
154
+ /**
155
+ * Resolve what belongs on the form. Stock grids are only those the template
156
+ * did not already bake and that no panel claimed.
157
+ */
158
+ export function ResolveFormContributions(input) {
159
+ const peers = visibleRelationships(input.RelatedEntities, input.IsaChildEntityIDs);
160
+ const collapsed = collapseRegistrations(applicableRegistrations(input.EntityName, input.Registrations));
161
+ const baked = new Set(input.BakedSectionKeys);
162
+ const claimedKeys = new Set();
163
+ const registered = [];
164
+ for (const [key, reg] of collapsed) {
165
+ const related = reg.Metadata.relatedEntity?.trim();
166
+ const peer = related
167
+ ? peers.find((rel) => {
168
+ if (rel.RelatedEntity !== related)
169
+ return false;
170
+ const wantJoin = StripJoinFieldBrackets(reg.Metadata.relatedJoinField);
171
+ if (!wantJoin)
172
+ return true;
173
+ return StripJoinFieldBrackets(rel.RelatedEntityJoinField) === wantJoin;
174
+ })
175
+ : undefined;
176
+ const sectionKey = peer ? RelatedEntitySectionKey(peer, peers) : '';
177
+ if (related) {
178
+ claimedKeys.add(key);
179
+ // A claim that omits the join field covers every FK to that entity.
180
+ if (!StripJoinFieldBrackets(reg.Metadata.relatedJoinField)) {
181
+ for (const match of peers.filter((rel) => rel.RelatedEntity === related)) {
182
+ claimedKeys.add(RelatedContributionKey(match.RelatedEntity, match.RelatedEntityJoinField));
183
+ }
184
+ }
185
+ }
186
+ registered.push(registeredWinner(key, reg, sectionKey, peer ? RelationshipDisplayName(peer) : related ?? key));
187
+ }
188
+ const hiddenBaked = [];
189
+ const stock = [];
190
+ if (input.ShowRelatedEntities) {
191
+ for (const rel of peers) {
192
+ const key = RelatedContributionKey(rel.RelatedEntity, rel.RelatedEntityJoinField);
193
+ const sectionKey = RelatedEntitySectionKey(rel, peers);
194
+ if (claimedKeys.has(key)) {
195
+ if (baked.has(sectionKey))
196
+ hiddenBaked.push(sectionKey);
197
+ continue;
198
+ }
199
+ if (baked.has(sectionKey))
200
+ continue;
201
+ stock.push(stockWinner(rel, peers));
202
+ }
203
+ }
204
+ else {
205
+ for (const rel of peers) {
206
+ const key = RelatedContributionKey(rel.RelatedEntity, rel.RelatedEntityJoinField);
207
+ const sectionKey = RelatedEntitySectionKey(rel, peers);
208
+ if (claimedKeys.has(key) && baked.has(sectionKey))
209
+ hiddenBaked.push(sectionKey);
210
+ }
211
+ }
212
+ return {
213
+ Winners: sortWinners([...registered, ...stock]),
214
+ HiddenBakedSectionKeys: hiddenBaked,
215
+ StockGrids: sortWinners(stock),
216
+ };
217
+ }
218
+ /** Section keys a form should hide because a winning contribution claimed them. */
219
+ export function ContributionHiddenSectionKeys(entityName, relatedEntities, isaChildEntityIDs, registrations) {
220
+ const resolved = ResolveFormContributions({
221
+ EntityName: entityName,
222
+ RelatedEntities: relatedEntities,
223
+ IsaChildEntityIDs: isaChildEntityIDs,
224
+ Registrations: registrations,
225
+ BakedSectionKeys: [],
226
+ ShowRelatedEntities: true,
227
+ });
228
+ const peers = visibleRelationships(relatedEntities, isaChildEntityIDs);
229
+ const keys = [];
230
+ for (const winner of resolved.Winners) {
231
+ if (winner.Kind !== 'registered')
232
+ continue;
233
+ if (winner.ReplacesSectionKey)
234
+ keys.push(winner.ReplacesSectionKey);
235
+ if (!winner.RelatedEntity)
236
+ continue;
237
+ const peer = peers.find((rel) => {
238
+ if (rel.RelatedEntity !== winner.RelatedEntity)
239
+ return false;
240
+ if (!winner.RelatedJoinField)
241
+ return true;
242
+ return StripJoinFieldBrackets(rel.RelatedEntityJoinField) === winner.RelatedJoinField;
243
+ });
244
+ if (peer)
245
+ keys.push(RelatedEntitySectionKey(peer, peers));
246
+ }
247
+ return keys;
248
+ }
249
+ /** @deprecated Use {@link ContributionHiddenSectionKeys}. */
250
+ export function ClaimedRelatedSectionKeys(entityName, relatedEntities, isaChildEntityIDs, registrations) {
251
+ return ContributionHiddenSectionKeys(entityName, relatedEntities, isaChildEntityIDs, registrations);
252
+ }
253
+ //# sourceMappingURL=form-contribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-contribution.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-contribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAqDpD,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB,CAAC,SAAoC;IACvE,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC5C,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACtD,IAAI,MAAM,GAAG,SAAS;SACjB,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;SAC/D,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC;IAC1B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,aAAqB,EAAE,SAAyB;IACnF,OAAO,WAAW,aAAa,CAAC,IAAI,EAAE,IAAI,sBAAsB,CAAC,SAAS,CAAC,EAAE,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAmC;IACtE,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,sBAAsB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACnC,YAA0C,EAC1C,kBAA2D;IAE3D,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAClD,UAAU,CAAC,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC,eAAe,CAAC,CACjE,CAAC;IACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,oBAAoB,CACvB,GAAG,YAAY,CAAC,aAAa,IAAI,sBAAsB,CAAC,YAAY,CAAC,sBAAsB,CAAC,EAAE,CACjG,CAAC;IACN,CAAC;IACD,OAAO,oBAAoB,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,YAA0C;IAC9E,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC/C,MAAM,GAAG,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC;IAC3E,OAAO,GAAG,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC;AACvE,CAAC;AAED,SAAS,UAAU,CAAC,YAA0C,EAAE,WAA8B;IAC1F,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,oBAAoB,CACzB,OAAgD,EAChD,WAA8B;IAE9B,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5F,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC;QAClC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,GAAG,IAAI,CAAC;QACtC,OAAO,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,aAAa,CAAC,gBAAwB,EAAE,UAAkB;IAC/D,OAAO,gBAAgB,KAAK,GAAG,IAAI,gBAAgB,KAAK,UAAU,CAAC;AACvE,CAAC;AAED,SAAS,uBAAuB,CAC5B,UAAkB,EAClB,aAAsD;IAEtD,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,oEAAoE;QACpE,oCAAoC;QACpC,0EAA0E;QAC1E,gDAAgD;QAChD,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,IAAI,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACpF,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAC1C,aAA2B;IAE3B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IACrC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,YAAY,WAAW,EAAE,EAAE,CAAC;QAChF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,qBAAqB,CAC1B,aAAsD;IAEtD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwC,CAAC;IAChE,KAAK,MAAM,GAAG,IAAI,8BAA8B,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QAClH,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAChB,YAA0C,EAC1C,KAA8C;IAE9C,MAAM,IAAI,GAAG,sBAAsB,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;IACzE,OAAO;QACH,eAAe,EAAE,sBAAsB,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC;QACzE,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtC,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,YAAY;QAClB,aAAa,EAAE,YAAY,CAAC,aAAa;QACzC,gBAAgB,EAAE,IAAI;QACtB,eAAe,EAAE,uBAAuB,CAAC,YAAY,EAAE,KAAK,CAAC;QAC7D,WAAW,EAAE,uBAAuB,CAAC,YAAY,CAAC;KACrD,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CACrB,GAAW,EACX,GAAiC,EACjC,UAAkB,EAClB,WAAmB;IAEnB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC1B,OAAO;QACH,eAAe,EAAE,GAAG;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,CAAC;QAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,YAAY;QAClB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;QACnG,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,SAAS;QAChE,eAAe,EAAE,UAAU;QAC3B,WAAW,EAAE,WAAW;KAC3B,CAAC;AACN,CAAC;AAED,SAAS,WAAW,CAAC,OAAiC;IAClD,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QAC1D,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;QAC9D,OAAO,CAAC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAoC;IACzE,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,qBAAqB,CAAC,uBAAuB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;IACxG,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAE9C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,MAAM,UAAU,GAA6B,EAAE,CAAC;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,OAAO;YAChB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;gBACf,IAAI,GAAG,CAAC,aAAa,KAAK,OAAO;oBAAE,OAAO,KAAK,CAAC;gBAChD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;gBACvE,IAAI,CAAC,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBAC3B,OAAO,sBAAsB,CAAC,GAAG,CAAC,sBAAsB,CAAC,KAAK,QAAQ,CAAC;YAC3E,CAAC,CAAC;YACJ,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,OAAO,EAAE,CAAC;YACV,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrB,oEAAoE;YACpE,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACzD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,aAAa,KAAK,OAAO,CAAC,EAAE,CAAC;oBACvE,WAAW,CAAC,GAAG,CAAC,sBAAsB,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBAC/F,CAAC;YACL,CAAC;QACL,CAAC;QACD,UAAU,CAAC,IAAI,CACX,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,CAChG,CAAC;IACN,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,KAAK,GAA6B,EAAE,CAAC;IAC3C,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;QAC5B,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,sBAAsB,CAAC,CAAC;YAClF,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;oBAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACxD,SAAS;YACb,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,SAAS;YACpC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,sBAAsB,CAAC,CAAC;YAClF,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpF,CAAC;IACL,CAAC;IAED,OAAO;QACH,OAAO,EAAE,WAAW,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC;QAC/C,sBAAsB,EAAE,WAAW;QACnC,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC;KACjC,CAAC;AACN,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,6BAA6B,CACzC,UAAkB,EAClB,eAAwD,EACxD,iBAAoC,EACpC,aAAsD;IAEtD,MAAM,QAAQ,GAAG,wBAAwB,CAAC;QACtC,UAAU,EAAE,UAAU;QACtB,eAAe,EAAE,eAAe;QAChC,iBAAiB,EAAE,iBAAiB;QACpC,aAAa,EAAE,aAAa;QAC5B,gBAAgB,EAAE,EAAE;QACpB,mBAAmB,EAAE,IAAI;KAC5B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,oBAAoB,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;IACvE,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QAC3C,IAAI,MAAM,CAAC,kBAAkB;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,aAAa;YAAE,SAAS;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC5B,IAAI,GAAG,CAAC,aAAa,KAAK,MAAM,CAAC,aAAa;gBAAE,OAAO,KAAK,CAAC;YAC7D,IAAI,CAAC,MAAM,CAAC,gBAAgB;gBAAE,OAAO,IAAI,CAAC;YAC1C,OAAO,sBAAsB,CAAC,GAAG,CAAC,sBAAsB,CAAC,KAAK,MAAM,CAAC,gBAAgB,CAAC;QAC1F,CAAC,CAAC,CAAC;QACH,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,yBAAyB,CACrC,UAAkB,EAClB,eAAwD,EACxD,iBAAoC,EACpC,aAAsD;IAEtD,OAAO,6BAA6B,CAAC,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;AACxG,CAAC","sourcesContent":["/**\n * Form contributions — pure resolve of what should appear on an entity form.\n *\n * A contribution is either a registered BaseFormPanel or the stock related-entity\n * grid. Related claims replace the stock/baked grid. No Angular, no ClassFactory\n * — the host queries registrations and feeds this.\n *\n * Section keys for related panels MUST match CodeGen's camelCase\n * (`angular-codegen.ts` `camelCase` + related-entity sectionKey). If they drift,\n * hide-baked and skip-baked miss and the user sees a double grid.\n */\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';\n\n/** Minimum relationship shape the composer reads. Satisfied by EntityRelationshipInfo. */\nexport interface FormContributionRelationship {\n RelatedEntity: string;\n RelatedEntityID: string;\n RelatedEntityJoinField: string;\n DisplayInForm: boolean;\n DisplayName?: string | null;\n Sequence?: number | null;\n}\n\nexport interface FormContributionRegistration {\n Priority: number;\n Metadata: FormPanelRegistrationMetadata;\n}\n\nexport interface ResolveFormContributionsInput {\n EntityName: string;\n RelatedEntities: readonly FormContributionRelationship[];\n /** IS-A child entity IDs — CodeGen skips these (shown in the ISA side panel). */\n IsaChildEntityIDs: readonly string[];\n Registrations: readonly FormContributionRegistration[];\n /** SectionKeys already projected as Variant=\"related-entity\" in the form template. */\n BakedSectionKeys: readonly string[];\n ShowRelatedEntities: boolean;\n}\n\nexport type FormContributionKind = 'registered' | 'stock-grid';\n\nexport interface FormContributionWinner {\n ContributionKey: string;\n Slot: FormPanelSlot;\n SortKey: number;\n Priority: number;\n Kind: FormContributionKind;\n RelatedEntity?: string;\n RelatedJoinField?: string;\n /** Field/other section this registered winner asked to hide. */\n ReplacesSectionKey?: string;\n BakedSectionKey: string;\n DisplayName: string;\n}\n\nexport interface ResolveFormContributionsResult {\n Winners: FormContributionWinner[];\n /** CodeGen section keys to hide because a registered panel claimed them. */\n HiddenBakedSectionKeys: string[];\n /** Stock grids the host should mount (unclaimed and not already baked). */\n StockGrids: FormContributionWinner[];\n}\n\n/** Strip wrapping [] from a join field, matching CodeGen's tab-name helper. */\nexport function StripJoinFieldBrackets(joinField: string | null | undefined): string {\n return (joinField ?? '').trim().replace(/^\\[/, '').replace(/\\]$/, '');\n}\n\n/**\n * camelCase + identifier sanitize. Byte-compatible with\n * `CodeGenLib` `angular-codegen.ts` `camelCase` — do not \"improve\".\n */\nexport function FormSectionCamelCase(str: string): string {\n const sanitized = str.replace(/[^a-zA-Z0-9\\s]/g, ' ');\n let result = sanitized\n .replace(/\\s(.)/g, (_match, char: string) => char.toUpperCase())\n .replace(/\\s/g, '')\n .replace(/^(.)/, (_match, char: string) => char.toLowerCase());\n if (/^\\d/.test(result)) {\n result = '_' + result;\n }\n return result.length === 0 ? 'section' : result;\n}\n\nexport function RelatedContributionKey(relatedEntity: string, joinField?: string | null): string {\n return `related:${relatedEntity.trim()}:${StripJoinFieldBrackets(joinField)}`;\n}\n\nexport function ResolveContributionKey(meta: FormPanelRegistrationMetadata): string {\n if (meta.contributionKey && meta.contributionKey.trim().length > 0) {\n return meta.contributionKey.trim();\n }\n if (meta.relatedEntity && meta.relatedEntity.trim().length > 0) {\n return RelatedContributionKey(meta.relatedEntity, meta.relatedJoinField);\n }\n return '';\n}\n\n/**\n * SectionKey CodeGen emits for a related-entity panel. When more than one\n * DisplayInForm relationship points at the same entity, the join field is\n * appended so BillTo / ShipTo do not collide.\n */\nexport function RelatedEntitySectionKey(\n relationship: FormContributionRelationship,\n displayInFormPeers: readonly FormContributionRelationship[],\n): string {\n const sameEntity = displayInFormPeers.filter((peer) =>\n UUIDsEqual(peer.RelatedEntityID, relationship.RelatedEntityID),\n );\n if (sameEntity.length > 1) {\n return FormSectionCamelCase(\n `${relationship.RelatedEntity} ${StripJoinFieldBrackets(relationship.RelatedEntityJoinField)}`,\n );\n }\n return FormSectionCamelCase(relationship.RelatedEntity);\n}\n\nexport function RelationshipDisplayName(relationship: FormContributionRelationship): string {\n const named = relationship.DisplayName?.trim();\n const raw = named && named.length > 0 ? named : relationship.RelatedEntity;\n return raw.replace(/^[A-Za-z][A-Za-z0-9_]*:\\s+/, '').trim() || raw;\n}\n\nfunction isIsaChild(relationship: FormContributionRelationship, isaChildIds: readonly string[]): boolean {\n return isaChildIds.some((id) => UUIDsEqual(id, relationship.RelatedEntityID));\n}\n\nfunction visibleRelationships(\n related: readonly FormContributionRelationship[],\n isaChildIds: readonly string[],\n): FormContributionRelationship[] {\n const visible = related.filter((rel) => rel.DisplayInForm && !isIsaChild(rel, isaChildIds));\n return [...visible].sort((a, b) => {\n const aSeq = a.Sequence ?? 999999;\n const bSeq = b.Sequence ?? 999999;\n if (aSeq !== bSeq) return aSeq - bSeq;\n return a.RelatedEntity.localeCompare(b.RelatedEntity);\n });\n}\n\nfunction entityMatches(registeredEntity: string, formEntity: string): boolean {\n return registeredEntity === '*' || registeredEntity === formEntity;\n}\n\nfunction applicableRegistrations(\n entityName: string,\n registrations: readonly FormContributionRegistration[],\n): FormContributionRegistration[] {\n return registrations.filter((reg) => {\n const entity = reg.Metadata.entity;\n if (!entity || !entityMatches(entity, entityName)) return false;\n // A related claim on entity:'*' would hide that grid on every form.\n // Claims must name the form entity.\n // Related / field-section claims on entity:'*' would hide panels on every\n // form. Those claims must name the form entity.\n if ((reg.Metadata.relatedEntity || reg.Metadata.replacesSectionKey) && entity === '*') {\n return false;\n }\n return true;\n });\n}\n\n/**\n * Last-wins collapse by contributionKey (or derived related key).\n * Highest Priority keeps the slot. Registrations without a key never collapse.\n */\nexport function CollapseFormPanelRegistrations<T extends { Priority: number; Metadata: FormPanelRegistrationMetadata }>(\n registrations: readonly T[],\n): T[] {\n const winners = new Map<string, T>();\n let uniqueIndex = 0;\n for (const reg of registrations) {\n const key = ResolveContributionKey(reg.Metadata) || `__unique:${uniqueIndex++}`;\n const incumbent = winners.get(key);\n if (!incumbent || reg.Priority > incumbent.Priority) {\n winners.set(key, reg);\n }\n }\n return [...winners.values()];\n}\n\nfunction collapseRegistrations(\n registrations: readonly FormContributionRegistration[],\n): Map<string, FormContributionRegistration> {\n const winners = new Map<string, FormContributionRegistration>();\n for (const reg of CollapseFormPanelRegistrations(registrations)) {\n const key = ResolveContributionKey(reg.Metadata) || `${reg.Metadata.entity}:${reg.Metadata.slot}:${reg.Priority}`;\n winners.set(key, reg);\n }\n return winners;\n}\n\nfunction stockWinner(\n relationship: FormContributionRelationship,\n peers: readonly FormContributionRelationship[],\n): FormContributionWinner {\n const join = StripJoinFieldBrackets(relationship.RelatedEntityJoinField);\n return {\n ContributionKey: RelatedContributionKey(relationship.RelatedEntity, join),\n Slot: 'after-related',\n SortKey: -(relationship.Sequence ?? 0),\n Priority: 0,\n Kind: 'stock-grid',\n RelatedEntity: relationship.RelatedEntity,\n RelatedJoinField: join,\n BakedSectionKey: RelatedEntitySectionKey(relationship, peers),\n DisplayName: RelationshipDisplayName(relationship),\n };\n}\n\nfunction registeredWinner(\n key: string,\n reg: FormContributionRegistration,\n sectionKey: string,\n displayName: string,\n): FormContributionWinner {\n const meta = reg.Metadata;\n return {\n ContributionKey: key,\n Slot: meta.slot,\n SortKey: meta.sortKey ?? 0,\n Priority: reg.Priority,\n Kind: 'registered',\n RelatedEntity: meta.relatedEntity,\n RelatedJoinField: meta.relatedJoinField ? StripJoinFieldBrackets(meta.relatedJoinField) : undefined,\n ReplacesSectionKey: meta.replacesSectionKey?.trim() || undefined,\n BakedSectionKey: sectionKey,\n DisplayName: displayName,\n };\n}\n\nfunction sortWinners(winners: FormContributionWinner[]): FormContributionWinner[] {\n return [...winners].sort((a, b) => {\n if (a.SortKey !== b.SortKey) return b.SortKey - a.SortKey;\n if (a.Priority !== b.Priority) return b.Priority - a.Priority;\n return a.ContributionKey.localeCompare(b.ContributionKey);\n });\n}\n\n/**\n * Resolve what belongs on the form. Stock grids are only those the template\n * did not already bake and that no panel claimed.\n */\nexport function ResolveFormContributions(input: ResolveFormContributionsInput): ResolveFormContributionsResult {\n const peers = visibleRelationships(input.RelatedEntities, input.IsaChildEntityIDs);\n const collapsed = collapseRegistrations(applicableRegistrations(input.EntityName, input.Registrations));\n const baked = new Set(input.BakedSectionKeys);\n\n const claimedKeys = new Set<string>();\n const registered: FormContributionWinner[] = [];\n for (const [key, reg] of collapsed) {\n const related = reg.Metadata.relatedEntity?.trim();\n const peer = related\n ? peers.find((rel) => {\n if (rel.RelatedEntity !== related) return false;\n const wantJoin = StripJoinFieldBrackets(reg.Metadata.relatedJoinField);\n if (!wantJoin) return true;\n return StripJoinFieldBrackets(rel.RelatedEntityJoinField) === wantJoin;\n })\n : undefined;\n const sectionKey = peer ? RelatedEntitySectionKey(peer, peers) : '';\n if (related) {\n claimedKeys.add(key);\n // A claim that omits the join field covers every FK to that entity.\n if (!StripJoinFieldBrackets(reg.Metadata.relatedJoinField)) {\n for (const match of peers.filter((rel) => rel.RelatedEntity === related)) {\n claimedKeys.add(RelatedContributionKey(match.RelatedEntity, match.RelatedEntityJoinField));\n }\n }\n }\n registered.push(\n registeredWinner(key, reg, sectionKey, peer ? RelationshipDisplayName(peer) : related ?? key),\n );\n }\n\n const hiddenBaked: string[] = [];\n const stock: FormContributionWinner[] = [];\n if (input.ShowRelatedEntities) {\n for (const rel of peers) {\n const key = RelatedContributionKey(rel.RelatedEntity, rel.RelatedEntityJoinField);\n const sectionKey = RelatedEntitySectionKey(rel, peers);\n if (claimedKeys.has(key)) {\n if (baked.has(sectionKey)) hiddenBaked.push(sectionKey);\n continue;\n }\n if (baked.has(sectionKey)) continue;\n stock.push(stockWinner(rel, peers));\n }\n } else {\n for (const rel of peers) {\n const key = RelatedContributionKey(rel.RelatedEntity, rel.RelatedEntityJoinField);\n const sectionKey = RelatedEntitySectionKey(rel, peers);\n if (claimedKeys.has(key) && baked.has(sectionKey)) hiddenBaked.push(sectionKey);\n }\n }\n\n return {\n Winners: sortWinners([...registered, ...stock]),\n HiddenBakedSectionKeys: hiddenBaked,\n StockGrids: sortWinners(stock),\n };\n}\n\n/** Section keys a form should hide because a winning contribution claimed them. */\nexport function ContributionHiddenSectionKeys(\n entityName: string,\n relatedEntities: readonly FormContributionRelationship[],\n isaChildEntityIDs: readonly string[],\n registrations: readonly FormContributionRegistration[],\n): string[] {\n const resolved = ResolveFormContributions({\n EntityName: entityName,\n RelatedEntities: relatedEntities,\n IsaChildEntityIDs: isaChildEntityIDs,\n Registrations: registrations,\n BakedSectionKeys: [],\n ShowRelatedEntities: true,\n });\n const peers = visibleRelationships(relatedEntities, isaChildEntityIDs);\n const keys: string[] = [];\n for (const winner of resolved.Winners) {\n if (winner.Kind !== 'registered') continue;\n if (winner.ReplacesSectionKey) keys.push(winner.ReplacesSectionKey);\n if (!winner.RelatedEntity) continue;\n const peer = peers.find((rel) => {\n if (rel.RelatedEntity !== winner.RelatedEntity) return false;\n if (!winner.RelatedJoinField) return true;\n return StripJoinFieldBrackets(rel.RelatedEntityJoinField) === winner.RelatedJoinField;\n });\n if (peer) keys.push(RelatedEntitySectionKey(peer, peers));\n }\n return keys;\n}\n\n/** @deprecated Use {@link ContributionHiddenSectionKeys}. */\nexport function ClaimedRelatedSectionKeys(\n entityName: string,\n relatedEntities: readonly FormContributionRelationship[],\n isaChildEntityIDs: readonly string[],\n registrations: readonly FormContributionRegistration[],\n): string[] {\n return ContributionHiddenSectionKeys(entityName, relatedEntities, isaChildEntityIDs, registrations);\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { OnChanges, 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 { type FormContributionWinner } from './form-contribution';
6
+ import * as i0 from "@angular/core";
7
+ /**
8
+ * Fills in related-entity grids that the form template did not bake and that
9
+ * no registered panel claimed. Lives in `<mj-record-form-container>` so every
10
+ * generated and custom form that uses the container picks it up.
11
+ *
12
+ * Claimed / extra panels still mount via `<mj-form-panel-slot>` — this host
13
+ * does not remount those (avoids doubles).
14
+ */
15
+ export declare class FormContributionsComponent implements OnChanges {
16
+ Record: BaseEntity;
17
+ FormComponent: BaseFormComponent;
18
+ FormContext?: FormContext;
19
+ BakedSectionKeys: string[];
20
+ ShowRelatedEntities: boolean;
21
+ StockGrids: FormContributionWinner[];
22
+ ngOnChanges(changes: SimpleChanges): void;
23
+ private refresh;
24
+ static ɵfac: i0.ɵɵFactoryDeclaration<FormContributionsComponent, never>;
25
+ static ɵcmp: i0.ɵɵComponentDeclaration<FormContributionsComponent, "mj-form-contributions", never, { "Record": { "alias": "Record"; "required": false; }; "FormComponent": { "alias": "FormComponent"; "required": false; }; "FormContext": { "alias": "FormContext"; "required": false; }; "BakedSectionKeys": { "alias": "BakedSectionKeys"; "required": false; }; "ShowRelatedEntities": { "alias": "ShowRelatedEntities"; "required": false; }; }, {}, never, never, false, never>;
26
+ }
27
+ //# sourceMappingURL=form-contributions.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-contributions.component.d.ts","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-contributions.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAEH,KAAK,sBAAsB,EAC9B,MAAM,qBAAqB,CAAC;;AAE7B;;;;;;;GAOG;AACH,qBAea,0BAA2B,YAAW,SAAS;IAC/C,MAAM,EAAG,UAAU,CAAC;IACpB,aAAa,EAAG,iBAAiB,CAAC;IAClC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAM;IAChC,mBAAmB,UAAQ;IAE7B,UAAU,EAAE,sBAAsB,EAAE,CAAM;IAE1C,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAOhD,OAAO,CAAC,OAAO;yCAhBN,0BAA0B;2CAA1B,0BAA0B;CAgCtC"}