@memberjunction/ng-base-forms 5.42.0 → 5.43.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-form.component.d.ts","sourceRoot":"","sources":["../../../src/lib/interactive-form/interactive-form.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,YAAY,EAAS,SAAS,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AAChI,OAAO,EAAE,UAAU,EAAE,YAAY,EAAqB,MAAM,sBAAsB,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAC5E,OAAO,EACH,aAAa,EAOhB,MAAM,mDAAmD,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAsB,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;AAE3D;;;;;;;;;;;;;;GAcG;AACH,qBAKa,wBAAyB,SAAQ,iBAAkB,YAAW,MAAM,EAAE,SAAS;IAExF;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,kBAAkB,CAAS;IAEnC,IACoB,MAAM,CAAC,KAAK,EAAE,UAAU,EAQ3C;IACD,IAAoB,MAAM,IAAI,UAAU,CAAyB;IAEjE;;;;OAIG;IACa,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElD;;;;;;;;;;OAUG;IACa,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;IAE3D;;;;;;;;;;;OAWG;IACa,WAAW,UAAS;IAEpC,2FAA2F;IACpF,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;IAElD,2EAA2E;IAC3E,OAAO,CAAC,UAAU,CAAuB;IAEzC;;;;;OAKG;IACH,IAAW,SAAS,IAAI,MAAM,GAAG,IAAI,CAA4B;IACjE,IAAW,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,EAIpC;IAEgB,gBAAgB,8BAAqC;IAEtE,mFAAmF;IACnF,OAAO,CAAC,gBAAgB,CAA+B;IAEvD;;;;;;;OAOG;IACiC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAEtE;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAA6C;IAExE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAEpC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B/C;;;OAGG;IACU,qBAAqB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7E;;;;OAIG;IACI,kBAAkB,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI;IAIjF;;;;;;;;OAQG;YACW,gBAAgB;
|
|
1
|
+
{"version":3,"file":"interactive-form.component.d.ts","sourceRoot":"","sources":["../../../src/lib/interactive-form/interactive-form.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,YAAY,EAAS,SAAS,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AAChI,OAAO,EAAE,UAAU,EAAE,YAAY,EAAqB,MAAM,sBAAsB,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAC5E,OAAO,EACH,aAAa,EAOhB,MAAM,mDAAmD,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAsB,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;AAE3D;;;;;;;;;;;;;;GAcG;AACH,qBAKa,wBAAyB,SAAQ,iBAAkB,YAAW,MAAM,EAAE,SAAS;IAExF;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,kBAAkB,CAAS;IAEnC,IACoB,MAAM,CAAC,KAAK,EAAE,UAAU,EAQ3C;IACD,IAAoB,MAAM,IAAI,UAAU,CAAyB;IAEjE;;;;OAIG;IACa,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElD;;;;;;;;;;OAUG;IACa,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;IAE3D;;;;;;;;;;;OAWG;IACa,WAAW,UAAS;IAEpC,2FAA2F;IACpF,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;IAElD,2EAA2E;IAC3E,OAAO,CAAC,UAAU,CAAuB;IAEzC;;;;;OAKG;IACH,IAAW,SAAS,IAAI,MAAM,GAAG,IAAI,CAA4B;IACjE,IAAW,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,EAIpC;IAEgB,gBAAgB,8BAAqC;IAEtE,mFAAmF;IACnF,OAAO,CAAC,gBAAgB,CAA+B;IAEvD;;;;;;;OAOG;IACiC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAEtE;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAA6C;IAExE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAEpC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B/C;;;OAGG;IACU,qBAAqB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7E;;;;OAIG;IACI,kBAAkB,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI;IAIjF;;;;;;;;OAQG;YACW,gBAAgB;IA4D9B,OAAO,CAAC,kBAAkB;YAOZ,kBAAkB;IAahC,OAAO,CAAC,6BAA6B;IAWrC;;;;;OAKG;IACa,aAAa,IAAI,IAAI;IAKrB,WAAW,IAAI,IAAI;IAKnC;;;;;;;;;;;;;OAaG;IACmB,UAAU,CAAC,qBAAqB,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBlF;;;;;;;;OAQG;IACa,UAAU,IAAI,IAAI;IAiBlC;;;;;;;;;;;OAWG;YACW,iBAAiB;IAoE/B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA6B5B,OAAO,CAAC,iBAAiB;yCAxchB,wBAAwB;2CAAxB,wBAAwB;CA+cpC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAMnD"}
|
|
@@ -273,13 +273,25 @@ export class InteractiveFormComponent extends BaseFormComponent {
|
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
275
275
|
const dirtyEntries = Object.entries(args.dirtyFields ?? {});
|
|
276
|
-
if (dirtyEntries.length === 0) {
|
|
277
|
-
// No
|
|
278
|
-
// the
|
|
279
|
-
//
|
|
276
|
+
if (dirtyEntries.length === 0 && this.record.IsSaved) {
|
|
277
|
+
// No dirty fields on an already-persisted record — a genuine no-op.
|
|
278
|
+
// Most likely the user clicked Save without changing anything, or the
|
|
279
|
+
// React draft didn't reach the host before RequestSave fired. Surface
|
|
280
|
+
// it so the user sees why their edits didn't land.
|
|
280
281
|
console.warn('[InteractiveFormComponent] BeforeSave fired with no dirtyFields. ' +
|
|
281
282
|
'Either the user clicked Save without changing anything, or the ' +
|
|
282
283
|
'React draft did not propagate before save fired.');
|
|
284
|
+
// Skip Save() + rebuildFormHostProps(): re-rendering the React form
|
|
285
|
+
// would re-fire BeforeSave on a form that emits it on mount/render —
|
|
286
|
+
// an infinite re-mount loop that starves the form's async data load
|
|
287
|
+
// (it unmounts before setState commits). Treat it as a no-op.
|
|
288
|
+
//
|
|
289
|
+
// Guard on IsSaved so this only short-circuits existing records. A
|
|
290
|
+
// NEW record with no dirty fields must still fall through to Save()
|
|
291
|
+
// so the insert is attempted (and any validation failure surfaced)
|
|
292
|
+
// rather than masked as a reported success with no row inserted.
|
|
293
|
+
this.resolvePendingSave(true);
|
|
294
|
+
return;
|
|
283
295
|
}
|
|
284
296
|
for (const [fieldName, value] of dirtyEntries) {
|
|
285
297
|
const field = this.record.Fields.find(f => f.Name.trim().toLowerCase() === fieldName.trim().toLowerCase());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-form.component.js","sourceRoot":"","sources":["../../../src/lib/interactive-form/interactive-form.component.ts","../../../src/lib/interactive-form/interactive-form.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAqB,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChI,OAAO,EAA4B,QAAQ,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAEH,cAAc,EACd,eAAe,EAIf,UAAU,GACb,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAoB,kBAAkB,EAAuB,MAAM,0BAA0B,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;;;;;ICVnD,8BAA6B;IACzB,uBAAgD;IAChD,4BAAM;IAAA,YAAe;IACzB,AADyB,iBAAO,EAC1B;;;IADI,eAAe;IAAf,sCAAe;;;;IAGzB,gDAKoD;IAAhD,AADA,6PAAkB,oCAA6B,KAAC,oPAC5B,iCAA0B,KAAC;IACnD,iBAAqB;;;IAHjB,AADA,gDAA2B,wCACK;;;IAKpC,8BAA8B;IAC1B,uBAA2C;IAC3C,4BAAM;IAAA,kCAAa;IACvB,AADuB,iBAAO,EACxB;;;IAJR,AARA,AALF,sHAAiB,+GAK4B,gGAQpC;;;IAbT,6FAkBC;;;IAYG,8BAA6B;IACzB,uBAAgD;IAChD,4BAAM;IAAA,YAAe;IACzB,AADyB,iBAAO,EAC1B;;;IADI,eAAe;IAAf,sCAAe;;;;IAGzB,gDAKoD;IAAhD,AADA,6PAAkB,oCAA6B,KAAC,oPAC5B,iCAA0B,KAAC;IACnD,iBAAqB;;;IAHjB,AADA,gDAA2B,wCACK;;;IAKpC,8BAA8B;IAC1B,uBAA2C;IAC3C,4BAAM;IAAA,kCAAa;IACvB,AADuB,iBAAO,EACxB;;;;IA1Bd,mDAO4D;IAAxD,AADA,AADA,AADA,AADA,yOAAY,6BAAsB,KAAC,oOAChB,0BAAmB,KAAC,oOACpB,0BAAmB,KAAC,sOACnB,2BAAoB,KAAC,oPACd,kCAA2B,KAAC;IAerD,AARA,AALF,sHAAiB,+GAK4B,gGAQpC;IAOb,iBAA2B;;;IA3BvB,AADA,sCAAiB,yBACK;IAOtB,cAkBC;IAlBD,6FAkBC;;;IA5BH,AAtBF,8FAAmB,uGAsBV;;;IAtBT,4CAqDC;;ADrCD;;;;;;;;;;;;;;GAcG;AAMH,MAAM,OAAO,wBAAyB,SAAQ,iBAAiB;IAE3D;;;;;;;;;OASG;IACK,OAAO,CAAc;IACrB,kBAAkB,GAAG,KAAK,CAAC;IAEnC,IACoB,MAAM,CAAC,KAAiB;QACxC,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO;QACnC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,IAAI,CAAC,kBAAkB,IAAI,KAAK,EAAE,CAAC;YACnC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC5B,CAAC;IACL,CAAC;IACD,IAAoB,MAAM,KAAiB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAEjE;;;;OAIG;IACa,WAAW,GAAkB,IAAI,CAAC;IAElD;;;;;;;;;;OAUG;IACa,aAAa,GAAyB,IAAI,CAAC;IAE3D;;;;;;;;;;;OAWG;IACa,WAAW,GAAG,KAAK,CAAC;IAEpC,2FAA2F;IACpF,aAAa,GAAyB,IAAI,CAAC;IAElD,2EAA2E;IACnE,UAAU,GAAkB,IAAI,CAAC;IAEzC;;;;;OAKG;IACH,IAAW,SAAS,KAAoB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,IAAW,SAAS,CAAC,CAAgB;QACjC,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU;YAAE,OAAO;QAClC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IAEgB,gBAAgB,GAAG,IAAI,YAAY,EAAiB,CAAC;IAEtE,mFAAmF;IAC3E,gBAAgB,GAA4B,EAAE,CAAC;IAEvD;;;;;;;OAOG;IACiC,cAAc,CAAoB;IAEtE;;;;;OAKG;IACK,mBAAmB,GAAwC,IAAI,CAAC;IAEvD,cAAc,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3C,WAAW,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAE1C,KAAK,CAAC,QAAQ;QAC1B,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvB,sEAAsE;QACtE,oEAAoE;QACpE,kEAAkE;QAClE,+DAA+D;QAC/D,+CAA+C;QAC/C,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,QAAQ,CAAC,4DAA4D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzH,IAAI,CAAC,SAAS,GAAE,mDAAmD,CAAC;YACpE,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7C,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAChC,CAAC;QACD,mEAAmE;QACnE,+DAA+D;QAC/D,oEAAoE;QACpE,kEAAkE;QAClE,sBAAsB;QACtB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,qEAAqE;QACrE,0DAA0D;QAC1D,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACnC,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,qBAAqB,CAAC,KAA0B;QACzD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,cAAc,CAAC,UAAU;gBAC1B,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAA6B,CAAC,CAAC;gBACjE,MAAM;YACV,KAAK,cAAc,CAAC,YAAY;gBAC5B,MAAM,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,OAA+B,CAAC,CAAC;gBACrE,MAAM;YACV,KAAK,cAAc,CAAC,uBAAuB;gBACvC,IAAI,CAAC,6BAA6B,CAAC,KAAK,CAAC,OAA0C,CAAC,CAAC;gBACrF,MAAM;YACV,8EAA8E;YAC9E,+EAA+E;QACnF,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,KAAgD;QACtE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IAChG,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,gBAAgB,CAAC,IAAwB;QACnD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,QAAQ,CAAC,6DAA6D,CAAC,CAAC;YACxE,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO;QACX,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAC5D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,iEAAiE;YACjE,iEAAiE;YACjE,iEAAiE;YACjE,OAAO,CAAC,IAAI,CACR,mEAAmE;gBACnE,iEAAiE;gBACjE,kDAAkD,CACrD,CAAC;QACN,CAAC;QACD,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CACtC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACpE,IAAI,KAAK,EAAE,CAAC;gBACR,uDAAuD;gBACvD,2DAA2D;gBAC3D,iCAAiC;gBACjC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,IAAI,CACR,6CAA6C,SAAS,mBAAmB;oBACzE,cAAc,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,sBAAsB,CAClE,CAAC;YACN,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,KAAK,EAAE,CAAC;YACR,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7C,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAChC,CAAC;aAAM,CAAC;YACJ,QAAQ,CAAC,6CAA6C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,IAAI,eAAe,EAAE,CAAC,CAAC;QAC1J,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAEO,kBAAkB,CAAC,OAAgB;QACvC,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QACpC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,IAA0B;QACvD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,OAAO;QACX,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,QAAQ,CAAC,+CAA+C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,IAAI,eAAe,EAAE,CAAC,CAAC;QAC5J,CAAC;IACL,CAAC;IAEO,6BAA6B,CAAC,IAAqC;QACvE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,EAAE,CAAC;YAChC,IAAI,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACa,aAAa;QACzB,KAAK,CAAC,aAAa,EAAE,CAAC;QACtB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAChC,CAAC;IAEe,WAAW;QACvB,KAAK,CAAC,WAAW,EAAE,CAAC;QACpB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAChC,CAAC;IAED;;;;;;;;;;;;;OAaG;IACa,KAAK,CAAC,UAAU,CAAC,qBAA8B;QAC3D,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC;YACjE,OAAO,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,OAAO,CAAU,OAAO,CAAC,EAAE;YAC9C,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC;QACvC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACD,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAChC,QAAQ,CAAC,2DAA2D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxH,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC;QACjC,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAAC;YACnC,IAAI,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;;OAQG;IACa,UAAU;QACtB,IAAI,IAAI,CAAC,cAAc,EAAE,SAAS,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC;gBACD,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;YACpE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,QAAQ,CAAC,6DAA6D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC9H,CAAC;YACD,+DAA+D;YAC/D,8DAA8D;YAC9D,gEAAgE;YAChE,kBAAkB;YAClB,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QACD,KAAK,CAAC,UAAU,EAAE,CAAC;IACvB,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,iBAAiB;QAC3B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAE,sDAAsD,CAAC;YACvE,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC;YACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC;YAClC,gEAAgE;YAChE,IAAI,CAAC;gBACD,MAAM,sBAAsB,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YACxE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,QAAQ,CAAC,mEAAmE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAChI,gEAAgE;YACpE,CAAC;YAED,IAAI,QAAQ,GAAkB,IAAI,CAAC;YACnC,IAAI,YAAgC,CAAC;YAErC,MAAM,MAAM,GAAG,sBAAsB,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9E,IAAI,MAAM,EAAE,CAAC;gBACT,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAC;gBAChC,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC;YAC/B,CAAC;iBAAM,CAAC;gBACJ,2DAA2D;gBAC3D,yDAAyD;gBACzD,gEAAgE;gBAChE,8DAA8D;gBAC9D,4BAA4B;gBAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;gBAClD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAsD;oBACjF,UAAU,EAAE,gBAAgB;oBAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC;oBACvC,WAAW,EAAE,OAAO,IAAI,CAAC,WAAW,GAAG;oBACvC,OAAO,EAAE,CAAC;oBACV,UAAU,EAAE,QAAQ;oBACpB,WAAW,EAAE,IAAI;iBACpB,EAAE,IAAI,CAAC,CAAC;gBACT,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;oBAC7C,IAAI,CAAC,SAAS,GAAE,aAAa,IAAI,CAAC,WAAW,aAAa,CAAC;oBAC3D,OAAO;gBACX,CAAC;gBACD,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;gBAC3C,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1C,CAAC;YAED,IAAI,CAAC;gBACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAkB,CAAC;YACzE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAI,CAAC,SAAS,GAAE,aAAa,YAAY,IAAI,IAAI,CAAC,WAAW,oCAAoC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpJ,OAAO;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACtB,IAAI,CAAC,SAAS,GAAE,aAAa,YAAY,IAAI,IAAI,CAAC,WAAW,8BAA8B,CAAC;gBAC5F,OAAO;YACX,CAAC;YACD,kEAAkE;YAClE,kEAAkE;YAClE,+CAA+C;YAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,IAAI,CAAC,SAAS,GAAE,aAAa,YAAY,IAAI,IAAI,CAAC,WAAW,yCAAyC,CAAC;gBACvG,OAAO;YACX,CAAC;QACL,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC;QACvC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,oBAAoB;QACxB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,OAAO;QACX,CAAC;QACD,MAAM,MAAM,GAA4B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC/D,qBAAqB,CAAC,mBAAmB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QAElE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;QAClC,MAAM,UAAU,GAAmC,CAAC,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC;YAClE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC5B,CAAC,CAAC,IAAI,CAAC;QAEX,IAAI,CAAC,aAAa,GAAG;YACjB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI;YACvC,UAAU;YACV,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAC5B,cAAc,EAAE;gBACZ,MAAM;gBACN,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI;gBAC9E,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI;aACpD;YACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;YACtE,OAAO,EAAE,IAAI,CAAC,WAAW;YACzB,SAAS,EAAE,IAAI,CAAC,aAAa;YAC7B,SAAS,EAAE,IAAI,CAAC,aAAa;SAChC,CAAC;IACN,CAAC;IAEO,iBAAiB,CAAC,EAAgB;QACtC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,EAAE,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;YACvC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;6QAlcQ,wBAAwB,yBAAxB,wBAAwB;6DAAxB,wBAAwB;;;;;;YCrCrC,gFAAc;;YAAd,qCAuDC;;;iFDlBY,wBAAwB;cALpC,SAAS;6BACM,KAAK,YACP,qBAAqB;;kBAkB9B,KAAK;;kBAiBL,KAAK;;kBAaL,KAAK;;kBAcL,KAAK;;kBAqBL,MAAM;;kBAaN,SAAS;mBAAC,gBAAgB;;kFA7FlB,wBAAwB;AAqcrC;;;GAGG;AACH,MAAM,UAAU,4BAA4B;IACxC,0CAA0C;IAC1C,IAAI,KAAgB,EAAE,CAAC;QACnB,6DAA6D;QAC7D,MAAM,CAAC,GAAY,wBAAwB,CAAC;IAChD,CAAC;AACL,CAAC","sourcesContent":["import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild, inject } from '@angular/core';\nimport { BaseEntity, CompositeKey, LogError, RunView } from '@memberjunction/core';\nimport { InteractiveFormsEngine } from '@memberjunction/core-entities';\nimport { ComponentSpec } from '@memberjunction/interactive-component-types';\nimport {\n FormHostProps,\n FormEventNames,\n FormMethodNames,\n FormBeforeSaveArgs,\n FormBeforeDeleteArgs,\n FormEditModeChangeRequestedArgs,\n isFormRole,\n} from '@memberjunction/interactive-component-types/forms';\nimport { SimpleEntityFieldInfo } from '@memberjunction/interactive-component-types';\nimport { MJReactComponent, ReactBridgeService, ReactComponentEvent } from '@memberjunction/ng-react';\nimport { BaseFormComponent } from '../base-form-component';\n\n/**\n * Wraps a Component (the IC kind — declared `componentRole: 'form'`) so it can\n * stand in for an Angular form on any entity.\n *\n * **Layering invariant:** the React component never touches BaseEntity. This\n * wrapper owns the entity lifecycle (`record.Save`, `record.Delete`, mode\n * transitions). The React component sees only the snapshot in\n * {@link FormHostProps} and communicates back via `BeforeSave` /\n * `BeforeDelete` / `EditModeChangeRequested` events plus its dirty-field diff.\n *\n * Instantiated by `SingleRecordComponent.LoadForm` when the resolver finds an\n * `EntityFormOverride` matching the user's scope. Behaves exactly like the\n * generated Angular form from the host's perspective: same `record` setter,\n * same `EditMode` toggle, same outputs.\n */\n@Component({\n standalone: false,\n selector: 'mj-interactive-form',\n templateUrl: './interactive-form.component.html',\n})\nexport class InteractiveFormComponent extends BaseFormComponent implements OnInit, OnDestroy {\n\n /**\n * The bound record. Backed by a setter so swapping records *after*\n * the component has mounted (e.g. Form Builder cockpit preview record\n * picker) triggers a fresh `originalSnapshot` capture + a\n * `formHostProps` rebuild — without this, the React form keeps\n * rendering the original record's values even after the input\n * changes. Guarded on `_recordInitialized` so the post-mount path\n * doesn't fire during the first input assignment (handled by\n * `ngOnInit`, which sets both `originalSnapshot` and `formHostProps`).\n */\n private _record!: BaseEntity;\n private _recordInitialized = false;\n\n @Input()\n public override set record(value: BaseEntity) {\n if (this._record === value) return;\n this._record = value;\n if (this._recordInitialized && value) {\n this.originalSnapshot = value.GetAll();\n this.rebuildFormHostProps();\n this.cdr.markForCheck();\n }\n }\n public override get record(): BaseEntity { return this._record; }\n\n /**\n * The Component row ID resolved for this (entity, user, roles) tuple. Set\n * by `SingleRecordComponent.LoadForm` after the resolver returns an\n * override.\n */\n @Input() public ComponentID: string | null = null;\n\n /**\n * Loaded ComponentSpec, parsed from the Component row's `Specification` JSON.\n *\n * Two ways to populate this:\n * 1. **Via `ComponentID`** (the standard path): set the input, ngOnInit\n * fetches the row from `MJ: Components` and assigns the parsed spec.\n * 2. **Direct assignment** (the artifact-viewer path): the caller already\n * has the spec in hand (parsed from an artifact JSON blob) and sets\n * `componentSpec` directly. In that case `ComponentID` should be left\n * null and the DB fetch is skipped.\n */\n @Input() public componentSpec: ComponentSpec | null = null;\n\n /**\n * When true, render the React component **without** the\n * `<mj-record-form-container>` wrapper — i.e. no toolbar (no Save /\n * Cancel / Edit / Delete / History / Tags / Lists).\n *\n * Used by the Form Builder cockpit's Preview tab where the toolbar's\n * actions don't apply (Save would save the *record*, but the cockpit's\n * own Save button saves the *spec*; History/Tags assume a real\n * persistent record, but the preview record can be a synthetic\n * NewRecord). Keeping just the form body produces a clean \"this is\n * what the form looks like\" view.\n */\n @Input() public previewMode = false;\n\n /** FormHostProps passed to the React component. Recomputed when record or mode changes. */\n public formHostProps: FormHostProps | null = null;\n\n /** Loaded-spec error (component row missing, JSON parse failure, etc.). */\n private _loadError: string | null = null;\n\n /**\n * Loaded-spec error (component row missing, JSON parse failure,\n * React-runtime bootstrap failure, etc.). Setter auto-emits\n * `LoadErrorChanged` so host surfaces (e.g. Form Builder cockpit\n * Preview tab) can show their own error state — retrospective fix #10.\n */\n public get loadError(): string | null { return this._loadError; }\n public set loadError(v: string | null) {\n if (v === this._loadError) return;\n this._loadError = v;\n this.LoadErrorChanged.emit(v);\n }\n\n @Output() public LoadErrorChanged = new EventEmitter<string | null>();\n\n /** Tracks last-known field snapshot for computing the dirty-field diff on save. */\n private originalSnapshot: Record<string, unknown> = {};\n\n /**\n * Reference to the mounted React component. Used by the SaveRecord /\n * CancelEdit overrides below to invoke the component's registered\n * `RequestSave` / `RequestCancel` methods when the host toolbar fires\n * Save/Cancel — so the single user-visible Save lives on the toolbar\n * (consistent with every other entity form in MJ) and not duplicated\n * inside the form body.\n */\n @ViewChild('reactComponent') public reactComponent?: MJReactComponent;\n\n /**\n * Promise resolver populated when `SaveRecord` invokes `RequestSave`\n * on the React component. `handleBeforeSave` resolves it after the\n * BeforeSave-driven entity save completes, so SaveRecord can return\n * a meaningful success boolean and honor `StopEditModeAfterSave`.\n */\n private pendingSaveResolver: ((success: boolean) => void) | null = null;\n\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly reactBridge = inject(ReactBridgeService);\n\n public override async ngOnInit(): Promise<void> {\n await super.ngOnInit();\n // Force the React bridge to be initialized BEFORE we let our template\n // mount the `<mj-react-component>` child. Without this, the child's\n // ngAfterViewInit can race the adapter bootstrap and throw \"React\n // runtime not initialized\" — which then prevents the form from\n // rendering and silently breaks the save flow.\n try {\n await this.reactBridge.getReactContext();\n } catch (err) {\n LogError(`InteractiveFormComponent: React bridge bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);\n this.loadError ='React runtime failed to load. Try a hard refresh.';\n return;\n }\n if (this.record) {\n this.originalSnapshot = this.record.GetAll();\n this.rebuildFormHostProps();\n }\n // Mark init complete so the `record` setter rebuilds on subsequent\n // input changes (post-mount record swaps from the Form Builder\n // cockpit preview picker, etc.). Before this flag flips, the setter\n // intentionally does nothing because ngOnInit already handles the\n // initial assignment.\n this._recordInitialized = true;\n // Direct-spec path (artifact viewer) takes precedence; DB fetch only\n // when no spec was supplied and we have an ID to look up.\n if (!this.componentSpec && this.ComponentID) {\n await this.loadComponentSpec();\n }\n }\n\n /**\n * Called by the React component via `mj-react-component`'s `componentEvent`\n * @Output. Maps the form-role event names to BaseEntity operations.\n */\n public async OnReactComponentEvent(event: ReactComponentEvent): Promise<void> {\n switch (event.type) {\n case FormEventNames.BeforeSave:\n await this.handleBeforeSave(event.payload as FormBeforeSaveArgs);\n break;\n case FormEventNames.BeforeDelete:\n await this.handleBeforeDelete(event.payload as FormBeforeDeleteArgs);\n break;\n case FormEventNames.EditModeChangeRequested:\n this.handleEditModeChangeRequested(event.payload as FormEditModeChangeRequestedArgs);\n break;\n // FieldChanged / DirtyStateChanged / ValidationChanged are read-only signals;\n // the wrapper doesn't need to react beyond what the component does internally.\n }\n }\n\n /**\n * Called when the form-role component drills into a related record via\n * `callbacks.OpenEntityRecord(...)`. Relays it upward as a `Navigate` event\n * (the same path normal FK links use) so the host opens the target record.\n */\n public OnOpenEntityRecord(event: { entityName: string; key: CompositeKey }): void {\n this.Navigate.emit({ Kind: 'record', EntityName: event.entityName, PrimaryKey: event.key });\n }\n\n /**\n * Apply the React component's dirty-field diff to the BaseEntity, save,\n * and refresh the snapshot. Errors surface via `LatestResult.CompleteMessage`\n * per the BaseEntity Save contract.\n *\n * Resolves `pendingSaveResolver` if {@link SaveRecord} initiated this\n * flow via the toolbar — so the toolbar can return a meaningful boolean\n * and honor `StopEditModeAfterSave`.\n */\n private async handleBeforeSave(args: FormBeforeSaveArgs): Promise<void> {\n if (args.cancel) {\n this.resolvePendingSave(false);\n return;\n }\n if (!this.record) {\n LogError('InteractiveFormComponent.handleBeforeSave: no record loaded');\n this.resolvePendingSave(false);\n return;\n }\n\n const dirtyEntries = Object.entries(args.dirtyFields ?? {});\n if (dirtyEntries.length === 0) {\n // No-op save — most likely cause is the React draft didn't reach\n // the host before RequestSave fired. Surface it so the user sees\n // why their edits didn't land, instead of silently \"succeeding\".\n console.warn(\n '[InteractiveFormComponent] BeforeSave fired with no dirtyFields. ' +\n 'Either the user clicked Save without changing anything, or the ' +\n 'React draft did not propagate before save fired.'\n );\n }\n for (const [fieldName, value] of dirtyEntries) {\n const field = this.record.Fields.find(f =>\n f.Name.trim().toLowerCase() === fieldName.trim().toLowerCase());\n if (field) {\n // Use the canonical field name from the lookup so case\n // mismatches between the React draft and the entity schema\n // don't silently drop the value.\n this.record.Set(field.Name, value);\n } else {\n console.warn(\n `[InteractiveFormComponent] Unknown field \"${fieldName}\" in dirtyFields ` +\n `for entity ${this.record.EntityInfo.Name}. Value not applied.`\n );\n }\n }\n\n const saved = await this.record.Save();\n if (saved) {\n this.originalSnapshot = this.record.GetAll();\n this.rebuildFormHostProps();\n } else {\n LogError(`InteractiveFormComponent: save failed for ${this.record.EntityInfo.Name}: ${this.record.LatestResult?.CompleteMessage ?? 'unknown error'}`);\n }\n this.resolvePendingSave(saved);\n }\n\n private resolvePendingSave(success: boolean): void {\n if (this.pendingSaveResolver) {\n this.pendingSaveResolver(success);\n this.pendingSaveResolver = null;\n }\n }\n\n private async handleBeforeDelete(args: FormBeforeDeleteArgs): Promise<void> {\n if (args.cancel) {\n return;\n }\n if (!this.record) {\n return;\n }\n const deleted = await this.record.Delete();\n if (!deleted) {\n LogError(`InteractiveFormComponent: delete failed for ${this.record.EntityInfo.Name}: ${this.record.LatestResult?.CompleteMessage ?? 'unknown error'}`);\n }\n }\n\n private handleEditModeChangeRequested(args: FormEditModeChangeRequestedArgs): void {\n if (args.cancel) {\n return;\n }\n if (args.requestedMode === 'edit') {\n this.StartEditMode();\n } else {\n this.EndEditMode();\n }\n }\n\n /**\n * Propagate Angular-toolbar-driven mode changes into the React component's\n * `FormHostProps.mode`. Without these overrides, clicking Edit on the\n * `<mj-record-form-container>` toolbar flips `this.EditMode` but the React\n * side still sees `mode: 'view'`.\n */\n public override StartEditMode(): void {\n super.StartEditMode();\n this.rebuildFormHostProps();\n }\n\n public override EndEditMode(): void {\n super.EndEditMode();\n this.rebuildFormHostProps();\n }\n\n /**\n * Toolbar **Save** override. Routes the click through the React\n * component's registered `RequestSave` method so the React layer can\n * (a) run its own validation, (b) flush its draft state, (c) emit\n * `BeforeSave` with the dirty-field diff — exactly the flow the\n * in-form Save button would trigger. The wrapper then awaits the\n * BeforeSave handler via `pendingSaveResolver`, gets a real\n * success/failure boolean, and honors `StopEditModeAfterSave`.\n *\n * Falls back to the base implementation for components that haven't\n * registered `RequestSave` — preserves correctness for forms that\n * keep their own in-form Save button (older sample, hand-authored\n * forms that pre-date this contract).\n */\n public override async SaveRecord(StopEditModeAfterSave: boolean): Promise<boolean> {\n if (!this.reactComponent?.hasMethod?.(FormMethodNames.RequestSave)) {\n return super.SaveRecord(StopEditModeAfterSave);\n }\n const completion = new Promise<boolean>(resolve => {\n this.pendingSaveResolver = resolve;\n });\n try {\n this.reactComponent.invokeMethod(FormMethodNames.RequestSave);\n } catch (err) {\n this.pendingSaveResolver = null;\n LogError(`InteractiveFormComponent.SaveRecord: RequestSave threw: ${err instanceof Error ? err.message : String(err)}`);\n return false;\n }\n const success = await completion;\n if (success && StopEditModeAfterSave) {\n this.EndEditMode();\n }\n return success;\n }\n\n /**\n * Toolbar **Cancel** override. Asks the React component to discard\n * its draft via `RequestCancel`, then flips out of edit mode the\n * same way the base implementation would. The React component is\n * expected to (a) clear its local draft state and (b) emit\n * `EditModeChangeRequested({ requestedMode: 'view' })` — though we\n * call `EndEditMode` here directly anyway, so even a no-op handler\n * leaves the form in a coherent state.\n */\n public override CancelEdit(): void {\n if (this.reactComponent?.hasMethod?.(FormMethodNames.RequestCancel)) {\n try {\n this.reactComponent.invokeMethod(FormMethodNames.RequestCancel);\n } catch (err) {\n LogError(`InteractiveFormComponent.CancelEdit: RequestCancel threw: ${err instanceof Error ? err.message : String(err)}`);\n }\n // Don't call super.CancelEdit() — base reverts the BaseEntity,\n // but for interactive forms the BaseEntity never received the\n // React draft in the first place, so there's nothing to revert.\n // Just flip mode.\n this.EndEditMode();\n return;\n }\n super.CancelEdit();\n }\n\n /**\n * Resolve the Component row, parse its `Specification`, and surface either\n * the spec or an error. Backed by `InteractiveFormsEngine` (in-memory\n * cache, BaseEntity event-driven invalidation) so this is a sub-ms JS\n * lookup once the engine has loaded — the engine handles freshness\n * via remote-invalidate events, replacing the prior RunView's\n * `BypassCache: true` escape hatch. Falls through to a one-shot\n * RunView only on engine-cache miss (defensive — the engine should\n * always have a Type='Form' Component, but if a brand-new Component\n * was just created on the server and the invalidation event hasn't\n * propagated yet, we still want to render rather than blank-screen).\n */\n private async loadComponentSpec(): Promise<void> {\n if (!this.ComponentID) {\n this.loadError ='No ComponentID provided to InteractiveFormComponent.';\n return;\n }\n try {\n const provider = this.ProviderToUse;\n const user = provider.CurrentUser;\n // Lazy-load the form-metadata cache. No-op when already loaded.\n try {\n await InteractiveFormsEngine.Instance.Config(false, user, provider);\n } catch (err) {\n LogError(`InteractiveFormComponent: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);\n // Fall through to RunView path below — engine miss isn't fatal.\n }\n\n let specJson: string | null = null;\n let nameForError: string | undefined;\n\n const cached = InteractiveFormsEngine.Instance.FindFormByID(this.ComponentID);\n if (cached) {\n specJson = cached.Specification;\n nameForError = cached.Name;\n } else {\n // Engine miss — one-shot RunView fallback. This covers the\n // narrow window where a freshly-created Component hasn't\n // propagated to the engine yet (cross-process remote-invalidate\n // is debounced ~1.5s). After the propagation lands subsequent\n // form opens hit the cache.\n const rv = RunView.FromMetadataProvider(provider);\n const result = await rv.RunView<{ ID: string; Specification: string; Name: string }>({\n EntityName: 'MJ: Components',\n Fields: ['ID', 'Name', 'Specification'],\n ExtraFilter: `ID='${this.ComponentID}'`,\n MaxRows: 1,\n ResultType: 'simple',\n BypassCache: true,\n }, user);\n if (!result.Success || !result.Results?.length) {\n this.loadError =`Component ${this.ComponentID} not found.`;\n return;\n }\n specJson = result.Results[0].Specification;\n nameForError = result.Results[0].Name;\n }\n\n try {\n this.componentSpec = JSON.parse(specJson ?? 'null') as ComponentSpec;\n } catch (err) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} has invalid Specification JSON: ${err instanceof Error ? err.message : String(err)}`;\n return;\n }\n if (!this.componentSpec) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} has an empty Specification.`;\n return;\n }\n // Validate the spec commits to the form-role contract. We use the\n // helper (whose signature is structural via Pick<>) so this stays\n // robust across ComponentSpec field additions.\n if (!isFormRole(this.componentSpec)) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} does not declare componentRole='form'.`;\n return;\n }\n } finally {\n this.changeDetector.markForCheck();\n }\n }\n\n /**\n * Build `FormHostProps` from the current record + permissions + edit mode.\n * Called whenever the record loads, mode changes, or save completes.\n */\n private rebuildFormHostProps(): void {\n if (!this.record) {\n this.formHostProps = null;\n return;\n }\n const fields: SimpleEntityFieldInfo[] = this.record.Fields.map(f =>\n SimpleEntityFieldInfo.FromEntityFieldInfo(f.EntityFieldInfo));\n\n const pk = this.record.PrimaryKey;\n const primaryKey: Record<string, unknown> | null = (pk && pk.HasValue)\n ? this.primaryKeyToPlain(pk)\n : null;\n\n this.formHostProps = {\n entityName: this.record.EntityInfo.Name,\n primaryKey,\n record: this.record.GetAll(),\n entityMetadata: {\n fields,\n displayName: this.record.EntityInfo.DisplayName ?? this.record.EntityInfo.Name,\n nameField: this.record.EntityInfo.NameField?.Name,\n },\n mode: this.EditMode ? 'edit' : (pk && pk.HasValue ? 'view' : 'create'),\n canEdit: this.UserCanEdit,\n canDelete: this.UserCanDelete,\n canCreate: this.UserCanCreate,\n };\n }\n\n private primaryKeyToPlain(pk: CompositeKey): Record<string, unknown> {\n const obj: Record<string, unknown> = {};\n for (const kvp of pk.KeyValuePairs ?? []) {\n obj[kvp.FieldName] = kvp.Value;\n }\n return obj;\n }\n}\n\n/**\n * Tree-shaking guard. Imported and called from public-api.ts so bundlers don't\n * drop the component when no consumer references it by name.\n */\nexport function LoadInteractiveFormComponent(): void {\n // Force the class reference to stay live.\n if (false as boolean) {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const _: unknown = InteractiveFormComponent;\n }\n}\n","@if (record) {\n@if (previewMode) {\n <!-- Preview mode — no toolbar wrap. Just the form body for clean\n \"what the form looks like\" previews (Form Builder cockpit). -->\n @if (loadError) {\n <div class=\"mj-error-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ loadError }}</span>\n </div>\n } @else if (componentSpec && formHostProps) {\n <mj-react-component\n #reactComponent\n [component]=\"componentSpec\"\n [componentProps]=\"formHostProps\"\n (componentEvent)=\"OnReactComponentEvent($event)\"\n (openEntityRecord)=\"OnOpenEntityRecord($event)\">\n </mj-react-component>\n } @else {\n <div class=\"mj-loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading form…</span>\n </div>\n }\n} @else {\n<mj-record-form-container\n [Record]=\"record\"\n [FormComponent]=\"this\"\n (Navigate)=\"OnFormNavigate($event)\"\n (DeleteRequested)=\"OnDeleteRequested()\"\n (FavoriteToggled)=\"OnFavoriteToggled()\"\n (HistoryRequested)=\"OnHistoryRequested()\"\n (ListManagementRequested)=\"OnListManagementRequested()\">\n\n @if (loadError) {\n <div class=\"mj-error-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ loadError }}</span>\n </div>\n } @else if (componentSpec && formHostProps) {\n <mj-react-component\n #reactComponent\n [component]=\"componentSpec\"\n [componentProps]=\"formHostProps\"\n (componentEvent)=\"OnReactComponentEvent($event)\"\n (openEntityRecord)=\"OnOpenEntityRecord($event)\">\n </mj-react-component>\n } @else {\n <div class=\"mj-loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading form…</span>\n </div>\n }\n\n</mj-record-form-container>\n}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"interactive-form.component.js","sourceRoot":"","sources":["../../../src/lib/interactive-form/interactive-form.component.ts","../../../src/lib/interactive-form/interactive-form.component.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild, inject } from '@angular/core';\nimport { BaseEntity, CompositeKey, LogError, RunView } from '@memberjunction/core';\nimport { InteractiveFormsEngine } from '@memberjunction/core-entities';\nimport { ComponentSpec } from '@memberjunction/interactive-component-types';\nimport {\n FormHostProps,\n FormEventNames,\n FormMethodNames,\n FormBeforeSaveArgs,\n FormBeforeDeleteArgs,\n FormEditModeChangeRequestedArgs,\n isFormRole,\n} from '@memberjunction/interactive-component-types/forms';\nimport { SimpleEntityFieldInfo } from '@memberjunction/interactive-component-types';\nimport { MJReactComponent, ReactBridgeService, ReactComponentEvent } from '@memberjunction/ng-react';\nimport { BaseFormComponent } from '../base-form-component';\n\n/**\n * Wraps a Component (the IC kind — declared `componentRole: 'form'`) so it can\n * stand in for an Angular form on any entity.\n *\n * **Layering invariant:** the React component never touches BaseEntity. This\n * wrapper owns the entity lifecycle (`record.Save`, `record.Delete`, mode\n * transitions). The React component sees only the snapshot in\n * {@link FormHostProps} and communicates back via `BeforeSave` /\n * `BeforeDelete` / `EditModeChangeRequested` events plus its dirty-field diff.\n *\n * Instantiated by `SingleRecordComponent.LoadForm` when the resolver finds an\n * `EntityFormOverride` matching the user's scope. Behaves exactly like the\n * generated Angular form from the host's perspective: same `record` setter,\n * same `EditMode` toggle, same outputs.\n */\n@Component({\n standalone: false,\n selector: 'mj-interactive-form',\n templateUrl: './interactive-form.component.html',\n})\nexport class InteractiveFormComponent extends BaseFormComponent implements OnInit, OnDestroy {\n\n /**\n * The bound record. Backed by a setter so swapping records *after*\n * the component has mounted (e.g. Form Builder cockpit preview record\n * picker) triggers a fresh `originalSnapshot` capture + a\n * `formHostProps` rebuild — without this, the React form keeps\n * rendering the original record's values even after the input\n * changes. Guarded on `_recordInitialized` so the post-mount path\n * doesn't fire during the first input assignment (handled by\n * `ngOnInit`, which sets both `originalSnapshot` and `formHostProps`).\n */\n private _record!: BaseEntity;\n private _recordInitialized = false;\n\n @Input()\n public override set record(value: BaseEntity) {\n if (this._record === value) return;\n this._record = value;\n if (this._recordInitialized && value) {\n this.originalSnapshot = value.GetAll();\n this.rebuildFormHostProps();\n this.cdr.markForCheck();\n }\n }\n public override get record(): BaseEntity { return this._record; }\n\n /**\n * The Component row ID resolved for this (entity, user, roles) tuple. Set\n * by `SingleRecordComponent.LoadForm` after the resolver returns an\n * override.\n */\n @Input() public ComponentID: string | null = null;\n\n /**\n * Loaded ComponentSpec, parsed from the Component row's `Specification` JSON.\n *\n * Two ways to populate this:\n * 1. **Via `ComponentID`** (the standard path): set the input, ngOnInit\n * fetches the row from `MJ: Components` and assigns the parsed spec.\n * 2. **Direct assignment** (the artifact-viewer path): the caller already\n * has the spec in hand (parsed from an artifact JSON blob) and sets\n * `componentSpec` directly. In that case `ComponentID` should be left\n * null and the DB fetch is skipped.\n */\n @Input() public componentSpec: ComponentSpec | null = null;\n\n /**\n * When true, render the React component **without** the\n * `<mj-record-form-container>` wrapper — i.e. no toolbar (no Save /\n * Cancel / Edit / Delete / History / Tags / Lists).\n *\n * Used by the Form Builder cockpit's Preview tab where the toolbar's\n * actions don't apply (Save would save the *record*, but the cockpit's\n * own Save button saves the *spec*; History/Tags assume a real\n * persistent record, but the preview record can be a synthetic\n * NewRecord). Keeping just the form body produces a clean \"this is\n * what the form looks like\" view.\n */\n @Input() public previewMode = false;\n\n /** FormHostProps passed to the React component. Recomputed when record or mode changes. */\n public formHostProps: FormHostProps | null = null;\n\n /** Loaded-spec error (component row missing, JSON parse failure, etc.). */\n private _loadError: string | null = null;\n\n /**\n * Loaded-spec error (component row missing, JSON parse failure,\n * React-runtime bootstrap failure, etc.). Setter auto-emits\n * `LoadErrorChanged` so host surfaces (e.g. Form Builder cockpit\n * Preview tab) can show their own error state — retrospective fix #10.\n */\n public get loadError(): string | null { return this._loadError; }\n public set loadError(v: string | null) {\n if (v === this._loadError) return;\n this._loadError = v;\n this.LoadErrorChanged.emit(v);\n }\n\n @Output() public LoadErrorChanged = new EventEmitter<string | null>();\n\n /** Tracks last-known field snapshot for computing the dirty-field diff on save. */\n private originalSnapshot: Record<string, unknown> = {};\n\n /**\n * Reference to the mounted React component. Used by the SaveRecord /\n * CancelEdit overrides below to invoke the component's registered\n * `RequestSave` / `RequestCancel` methods when the host toolbar fires\n * Save/Cancel — so the single user-visible Save lives on the toolbar\n * (consistent with every other entity form in MJ) and not duplicated\n * inside the form body.\n */\n @ViewChild('reactComponent') public reactComponent?: MJReactComponent;\n\n /**\n * Promise resolver populated when `SaveRecord` invokes `RequestSave`\n * on the React component. `handleBeforeSave` resolves it after the\n * BeforeSave-driven entity save completes, so SaveRecord can return\n * a meaningful success boolean and honor `StopEditModeAfterSave`.\n */\n private pendingSaveResolver: ((success: boolean) => void) | null = null;\n\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly reactBridge = inject(ReactBridgeService);\n\n public override async ngOnInit(): Promise<void> {\n await super.ngOnInit();\n // Force the React bridge to be initialized BEFORE we let our template\n // mount the `<mj-react-component>` child. Without this, the child's\n // ngAfterViewInit can race the adapter bootstrap and throw \"React\n // runtime not initialized\" — which then prevents the form from\n // rendering and silently breaks the save flow.\n try {\n await this.reactBridge.getReactContext();\n } catch (err) {\n LogError(`InteractiveFormComponent: React bridge bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);\n this.loadError ='React runtime failed to load. Try a hard refresh.';\n return;\n }\n if (this.record) {\n this.originalSnapshot = this.record.GetAll();\n this.rebuildFormHostProps();\n }\n // Mark init complete so the `record` setter rebuilds on subsequent\n // input changes (post-mount record swaps from the Form Builder\n // cockpit preview picker, etc.). Before this flag flips, the setter\n // intentionally does nothing because ngOnInit already handles the\n // initial assignment.\n this._recordInitialized = true;\n // Direct-spec path (artifact viewer) takes precedence; DB fetch only\n // when no spec was supplied and we have an ID to look up.\n if (!this.componentSpec && this.ComponentID) {\n await this.loadComponentSpec();\n }\n }\n\n /**\n * Called by the React component via `mj-react-component`'s `componentEvent`\n * @Output. Maps the form-role event names to BaseEntity operations.\n */\n public async OnReactComponentEvent(event: ReactComponentEvent): Promise<void> {\n switch (event.type) {\n case FormEventNames.BeforeSave:\n await this.handleBeforeSave(event.payload as FormBeforeSaveArgs);\n break;\n case FormEventNames.BeforeDelete:\n await this.handleBeforeDelete(event.payload as FormBeforeDeleteArgs);\n break;\n case FormEventNames.EditModeChangeRequested:\n this.handleEditModeChangeRequested(event.payload as FormEditModeChangeRequestedArgs);\n break;\n // FieldChanged / DirtyStateChanged / ValidationChanged are read-only signals;\n // the wrapper doesn't need to react beyond what the component does internally.\n }\n }\n\n /**\n * Called when the form-role component drills into a related record via\n * `callbacks.OpenEntityRecord(...)`. Relays it upward as a `Navigate` event\n * (the same path normal FK links use) so the host opens the target record.\n */\n public OnOpenEntityRecord(event: { entityName: string; key: CompositeKey }): void {\n this.Navigate.emit({ Kind: 'record', EntityName: event.entityName, PrimaryKey: event.key });\n }\n\n /**\n * Apply the React component's dirty-field diff to the BaseEntity, save,\n * and refresh the snapshot. Errors surface via `LatestResult.CompleteMessage`\n * per the BaseEntity Save contract.\n *\n * Resolves `pendingSaveResolver` if {@link SaveRecord} initiated this\n * flow via the toolbar — so the toolbar can return a meaningful boolean\n * and honor `StopEditModeAfterSave`.\n */\n private async handleBeforeSave(args: FormBeforeSaveArgs): Promise<void> {\n if (args.cancel) {\n this.resolvePendingSave(false);\n return;\n }\n if (!this.record) {\n LogError('InteractiveFormComponent.handleBeforeSave: no record loaded');\n this.resolvePendingSave(false);\n return;\n }\n\n const dirtyEntries = Object.entries(args.dirtyFields ?? {});\n if (dirtyEntries.length === 0 && this.record.IsSaved) {\n // No dirty fields on an already-persisted record — a genuine no-op.\n // Most likely the user clicked Save without changing anything, or the\n // React draft didn't reach the host before RequestSave fired. Surface\n // it so the user sees why their edits didn't land.\n console.warn(\n '[InteractiveFormComponent] BeforeSave fired with no dirtyFields. ' +\n 'Either the user clicked Save without changing anything, or the ' +\n 'React draft did not propagate before save fired.'\n );\n // Skip Save() + rebuildFormHostProps(): re-rendering the React form\n // would re-fire BeforeSave on a form that emits it on mount/render —\n // an infinite re-mount loop that starves the form's async data load\n // (it unmounts before setState commits). Treat it as a no-op.\n //\n // Guard on IsSaved so this only short-circuits existing records. A\n // NEW record with no dirty fields must still fall through to Save()\n // so the insert is attempted (and any validation failure surfaced)\n // rather than masked as a reported success with no row inserted.\n this.resolvePendingSave(true);\n return;\n }\n for (const [fieldName, value] of dirtyEntries) {\n const field = this.record.Fields.find(f =>\n f.Name.trim().toLowerCase() === fieldName.trim().toLowerCase());\n if (field) {\n // Use the canonical field name from the lookup so case\n // mismatches between the React draft and the entity schema\n // don't silently drop the value.\n this.record.Set(field.Name, value);\n } else {\n console.warn(\n `[InteractiveFormComponent] Unknown field \"${fieldName}\" in dirtyFields ` +\n `for entity ${this.record.EntityInfo.Name}. Value not applied.`\n );\n }\n }\n\n const saved = await this.record.Save();\n if (saved) {\n this.originalSnapshot = this.record.GetAll();\n this.rebuildFormHostProps();\n } else {\n LogError(`InteractiveFormComponent: save failed for ${this.record.EntityInfo.Name}: ${this.record.LatestResult?.CompleteMessage ?? 'unknown error'}`);\n }\n this.resolvePendingSave(saved);\n }\n\n private resolvePendingSave(success: boolean): void {\n if (this.pendingSaveResolver) {\n this.pendingSaveResolver(success);\n this.pendingSaveResolver = null;\n }\n }\n\n private async handleBeforeDelete(args: FormBeforeDeleteArgs): Promise<void> {\n if (args.cancel) {\n return;\n }\n if (!this.record) {\n return;\n }\n const deleted = await this.record.Delete();\n if (!deleted) {\n LogError(`InteractiveFormComponent: delete failed for ${this.record.EntityInfo.Name}: ${this.record.LatestResult?.CompleteMessage ?? 'unknown error'}`);\n }\n }\n\n private handleEditModeChangeRequested(args: FormEditModeChangeRequestedArgs): void {\n if (args.cancel) {\n return;\n }\n if (args.requestedMode === 'edit') {\n this.StartEditMode();\n } else {\n this.EndEditMode();\n }\n }\n\n /**\n * Propagate Angular-toolbar-driven mode changes into the React component's\n * `FormHostProps.mode`. Without these overrides, clicking Edit on the\n * `<mj-record-form-container>` toolbar flips `this.EditMode` but the React\n * side still sees `mode: 'view'`.\n */\n public override StartEditMode(): void {\n super.StartEditMode();\n this.rebuildFormHostProps();\n }\n\n public override EndEditMode(): void {\n super.EndEditMode();\n this.rebuildFormHostProps();\n }\n\n /**\n * Toolbar **Save** override. Routes the click through the React\n * component's registered `RequestSave` method so the React layer can\n * (a) run its own validation, (b) flush its draft state, (c) emit\n * `BeforeSave` with the dirty-field diff — exactly the flow the\n * in-form Save button would trigger. The wrapper then awaits the\n * BeforeSave handler via `pendingSaveResolver`, gets a real\n * success/failure boolean, and honors `StopEditModeAfterSave`.\n *\n * Falls back to the base implementation for components that haven't\n * registered `RequestSave` — preserves correctness for forms that\n * keep their own in-form Save button (older sample, hand-authored\n * forms that pre-date this contract).\n */\n public override async SaveRecord(StopEditModeAfterSave: boolean): Promise<boolean> {\n if (!this.reactComponent?.hasMethod?.(FormMethodNames.RequestSave)) {\n return super.SaveRecord(StopEditModeAfterSave);\n }\n const completion = new Promise<boolean>(resolve => {\n this.pendingSaveResolver = resolve;\n });\n try {\n this.reactComponent.invokeMethod(FormMethodNames.RequestSave);\n } catch (err) {\n this.pendingSaveResolver = null;\n LogError(`InteractiveFormComponent.SaveRecord: RequestSave threw: ${err instanceof Error ? err.message : String(err)}`);\n return false;\n }\n const success = await completion;\n if (success && StopEditModeAfterSave) {\n this.EndEditMode();\n }\n return success;\n }\n\n /**\n * Toolbar **Cancel** override. Asks the React component to discard\n * its draft via `RequestCancel`, then flips out of edit mode the\n * same way the base implementation would. The React component is\n * expected to (a) clear its local draft state and (b) emit\n * `EditModeChangeRequested({ requestedMode: 'view' })` — though we\n * call `EndEditMode` here directly anyway, so even a no-op handler\n * leaves the form in a coherent state.\n */\n public override CancelEdit(): void {\n if (this.reactComponent?.hasMethod?.(FormMethodNames.RequestCancel)) {\n try {\n this.reactComponent.invokeMethod(FormMethodNames.RequestCancel);\n } catch (err) {\n LogError(`InteractiveFormComponent.CancelEdit: RequestCancel threw: ${err instanceof Error ? err.message : String(err)}`);\n }\n // Don't call super.CancelEdit() — base reverts the BaseEntity,\n // but for interactive forms the BaseEntity never received the\n // React draft in the first place, so there's nothing to revert.\n // Just flip mode.\n this.EndEditMode();\n return;\n }\n super.CancelEdit();\n }\n\n /**\n * Resolve the Component row, parse its `Specification`, and surface either\n * the spec or an error. Backed by `InteractiveFormsEngine` (in-memory\n * cache, BaseEntity event-driven invalidation) so this is a sub-ms JS\n * lookup once the engine has loaded — the engine handles freshness\n * via remote-invalidate events, replacing the prior RunView's\n * `BypassCache: true` escape hatch. Falls through to a one-shot\n * RunView only on engine-cache miss (defensive — the engine should\n * always have a Type='Form' Component, but if a brand-new Component\n * was just created on the server and the invalidation event hasn't\n * propagated yet, we still want to render rather than blank-screen).\n */\n private async loadComponentSpec(): Promise<void> {\n if (!this.ComponentID) {\n this.loadError ='No ComponentID provided to InteractiveFormComponent.';\n return;\n }\n try {\n const provider = this.ProviderToUse;\n const user = provider.CurrentUser;\n // Lazy-load the form-metadata cache. No-op when already loaded.\n try {\n await InteractiveFormsEngine.Instance.Config(false, user, provider);\n } catch (err) {\n LogError(`InteractiveFormComponent: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);\n // Fall through to RunView path below — engine miss isn't fatal.\n }\n\n let specJson: string | null = null;\n let nameForError: string | undefined;\n\n const cached = InteractiveFormsEngine.Instance.FindFormByID(this.ComponentID);\n if (cached) {\n specJson = cached.Specification;\n nameForError = cached.Name;\n } else {\n // Engine miss — one-shot RunView fallback. This covers the\n // narrow window where a freshly-created Component hasn't\n // propagated to the engine yet (cross-process remote-invalidate\n // is debounced ~1.5s). After the propagation lands subsequent\n // form opens hit the cache.\n const rv = RunView.FromMetadataProvider(provider);\n const result = await rv.RunView<{ ID: string; Specification: string; Name: string }>({\n EntityName: 'MJ: Components',\n Fields: ['ID', 'Name', 'Specification'],\n ExtraFilter: `ID='${this.ComponentID}'`,\n MaxRows: 1,\n ResultType: 'simple',\n BypassCache: true,\n }, user);\n if (!result.Success || !result.Results?.length) {\n this.loadError =`Component ${this.ComponentID} not found.`;\n return;\n }\n specJson = result.Results[0].Specification;\n nameForError = result.Results[0].Name;\n }\n\n try {\n this.componentSpec = JSON.parse(specJson ?? 'null') as ComponentSpec;\n } catch (err) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} has invalid Specification JSON: ${err instanceof Error ? err.message : String(err)}`;\n return;\n }\n if (!this.componentSpec) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} has an empty Specification.`;\n return;\n }\n // Validate the spec commits to the form-role contract. We use the\n // helper (whose signature is structural via Pick<>) so this stays\n // robust across ComponentSpec field additions.\n if (!isFormRole(this.componentSpec)) {\n this.loadError =`Component ${nameForError ?? this.ComponentID} does not declare componentRole='form'.`;\n return;\n }\n } finally {\n this.changeDetector.markForCheck();\n }\n }\n\n /**\n * Build `FormHostProps` from the current record + permissions + edit mode.\n * Called whenever the record loads, mode changes, or save completes.\n */\n private rebuildFormHostProps(): void {\n if (!this.record) {\n this.formHostProps = null;\n return;\n }\n const fields: SimpleEntityFieldInfo[] = this.record.Fields.map(f =>\n SimpleEntityFieldInfo.FromEntityFieldInfo(f.EntityFieldInfo));\n\n const pk = this.record.PrimaryKey;\n const primaryKey: Record<string, unknown> | null = (pk && pk.HasValue)\n ? this.primaryKeyToPlain(pk)\n : null;\n\n this.formHostProps = {\n entityName: this.record.EntityInfo.Name,\n primaryKey,\n record: this.record.GetAll(),\n entityMetadata: {\n fields,\n displayName: this.record.EntityInfo.DisplayName ?? this.record.EntityInfo.Name,\n nameField: this.record.EntityInfo.NameField?.Name,\n },\n mode: this.EditMode ? 'edit' : (pk && pk.HasValue ? 'view' : 'create'),\n canEdit: this.UserCanEdit,\n canDelete: this.UserCanDelete,\n canCreate: this.UserCanCreate,\n };\n }\n\n private primaryKeyToPlain(pk: CompositeKey): Record<string, unknown> {\n const obj: Record<string, unknown> = {};\n for (const kvp of pk.KeyValuePairs ?? []) {\n obj[kvp.FieldName] = kvp.Value;\n }\n return obj;\n }\n}\n\n/**\n * Tree-shaking guard. Imported and called from public-api.ts so bundlers don't\n * drop the component when no consumer references it by name.\n */\nexport function LoadInteractiveFormComponent(): void {\n // Force the class reference to stay live.\n if (false as boolean) {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const _: unknown = InteractiveFormComponent;\n }\n}\n","@if (record) {\n@if (previewMode) {\n <!-- Preview mode — no toolbar wrap. Just the form body for clean\n \"what the form looks like\" previews (Form Builder cockpit). -->\n @if (loadError) {\n <div class=\"mj-error-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ loadError }}</span>\n </div>\n } @else if (componentSpec && formHostProps) {\n <mj-react-component\n #reactComponent\n [component]=\"componentSpec\"\n [componentProps]=\"formHostProps\"\n (componentEvent)=\"OnReactComponentEvent($event)\"\n (openEntityRecord)=\"OnOpenEntityRecord($event)\">\n </mj-react-component>\n } @else {\n <div class=\"mj-loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading form…</span>\n </div>\n }\n} @else {\n<mj-record-form-container\n [Record]=\"record\"\n [FormComponent]=\"this\"\n (Navigate)=\"OnFormNavigate($event)\"\n (DeleteRequested)=\"OnDeleteRequested()\"\n (FavoriteToggled)=\"OnFavoriteToggled()\"\n (HistoryRequested)=\"OnHistoryRequested()\"\n (ListManagementRequested)=\"OnListManagementRequested()\">\n\n @if (loadError) {\n <div class=\"mj-error-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ loadError }}</span>\n </div>\n } @else if (componentSpec && formHostProps) {\n <mj-react-component\n #reactComponent\n [component]=\"componentSpec\"\n [componentProps]=\"formHostProps\"\n (componentEvent)=\"OnReactComponentEvent($event)\"\n (openEntityRecord)=\"OnOpenEntityRecord($event)\">\n </mj-react-component>\n } @else {\n <div class=\"mj-loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading form…</span>\n </div>\n }\n\n</mj-record-form-container>\n}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-base-forms",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.43.0",
|
|
4
4
|
"description": "MemberJunction: Generic Angular form components for rendering and editing entity records. Provides configurable toolbar, modern form fields (PrimeNG unstyled), collapsible panels with IS-A inheritance support, and event-driven navigation. Zero Explorer dependencies - usable in any Angular application.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -36,21 +36,21 @@
|
|
|
36
36
|
"primeng": "21.1.1"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@memberjunction/core": "5.
|
|
40
|
-
"@memberjunction/core-entities": "5.
|
|
41
|
-
"@memberjunction/global": "5.
|
|
42
|
-
"@memberjunction/interactive-component-types": "5.
|
|
43
|
-
"@memberjunction/ng-base-types": "5.
|
|
44
|
-
"@memberjunction/ng-code-editor": "5.
|
|
45
|
-
"@memberjunction/ng-entity-viewer": "5.
|
|
46
|
-
"@memberjunction/ng-list-management": "5.
|
|
47
|
-
"@memberjunction/ng-markdown": "5.
|
|
48
|
-
"@memberjunction/ng-notifications": "5.
|
|
49
|
-
"@memberjunction/ng-react": "5.
|
|
50
|
-
"@memberjunction/ng-record-changes": "5.
|
|
51
|
-
"@memberjunction/ng-record-tags": "5.
|
|
52
|
-
"@memberjunction/ng-shared-generic": "5.
|
|
53
|
-
"@memberjunction/ng-ui-components": "5.
|
|
39
|
+
"@memberjunction/core": "5.43.0",
|
|
40
|
+
"@memberjunction/core-entities": "5.43.0",
|
|
41
|
+
"@memberjunction/global": "5.43.0",
|
|
42
|
+
"@memberjunction/interactive-component-types": "5.43.0",
|
|
43
|
+
"@memberjunction/ng-base-types": "5.43.0",
|
|
44
|
+
"@memberjunction/ng-code-editor": "5.43.0",
|
|
45
|
+
"@memberjunction/ng-entity-viewer": "5.43.0",
|
|
46
|
+
"@memberjunction/ng-list-management": "5.43.0",
|
|
47
|
+
"@memberjunction/ng-markdown": "5.43.0",
|
|
48
|
+
"@memberjunction/ng-notifications": "5.43.0",
|
|
49
|
+
"@memberjunction/ng-react": "5.43.0",
|
|
50
|
+
"@memberjunction/ng-record-changes": "5.43.0",
|
|
51
|
+
"@memberjunction/ng-record-tags": "5.43.0",
|
|
52
|
+
"@memberjunction/ng-shared-generic": "5.43.0",
|
|
53
|
+
"@memberjunction/ng-ui-components": "5.43.0",
|
|
54
54
|
"rxjs": "^7.8.2",
|
|
55
55
|
"tslib": "^2.8.1"
|
|
56
56
|
},
|