@memberjunction/ng-base-forms 5.42.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/container/record-form-container.component.js +18 -35
- package/dist/lib/container/record-form-container.component.js.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.js +6 -3
- package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
- package/dist/lib/interactive-form/interactive-form.component.d.ts.map +1 -1
- package/dist/lib/interactive-form/interactive-form.component.js +37 -28
- package/dist/lib/interactive-form/interactive-form.component.js.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.d.ts +6 -3
- package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts +9 -1
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -1
- package/dist/lib/panel-slot/form-panel-slot.component.js +13 -3
- package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -1
- package/dist/lib/resolver/form-resolver.service.d.ts.map +1 -1
- package/dist/lib/resolver/form-resolver.service.js +5 -0
- package/dist/lib/resolver/form-resolver.service.js.map +1 -1
- package/dist/module.d.ts +10 -9
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +7 -0
- package/dist/module.js.map +1 -1
- package/package.json +19 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-panel-slot.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ComponentRef,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n SimpleChanges,\n Type,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\nimport { BaseEntity, LogError } from '@memberjunction/core';\nimport { ClassRegistration, MJGlobal } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { BaseFormPanel, FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';\nimport { FormSlotCoordinator } from './form-slot-coordinator.service';\n\n/**\n * `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every\n * registered `BaseFormPanel` whose metadata matches the slot's `Entity` +\n * `Slot` inputs. Generated entity-form HTML emits one of these at each\n * well-known slot position so consumers can extend the form WITHOUT replacing\n * it via the custom-form override pattern.\n *\n * **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`\n * to find every panel registered with metadata `{ entity, slot }` matching this\n * slot. Registrations without a metadata bag (or without these required keys)\n * are silently ignored so legacy registrations don't accidentally bind.\n *\n * **Ordering**: registrations are sorted by `metadata.sortKey` descending —\n * higher number renders first within the slot. Use ranges (100/50/10) so future\n * panels can wedge in without renumbering. Ties fall back to the\n * `ClassRegistration.Priority` field (also descending), and finally to\n * registration order.\n *\n * **Fallback chain** (see `FormSlotCoordinator`): each slot host registers\n * with the per-form coordinator and additionally renders panels whose\n * REGISTERED slot is missing in the current form, when this host happens to\n * be the next-existing slot down the chain. The container template guarantees\n * an `after-everything` slot host, so fallback always terminates somewhere.\n * Downstream consumers running an older form template (no CodeGen rerun\n * yet) get every panel rendered at the bottom; consumers on the new template\n * get them in the preferred position.\n *\n * **Re-mount**: any input change OR a coordinator change (another slot\n * registers/deregisters) triggers a re-render so fallback assignments stay\n * correct as slots come and go.\n */\n@Component({\n standalone: false,\n selector: 'mj-form-panel-slot',\n template: `<ng-container #anchor></ng-container>`,\n // `display: contents` makes the host element disappear from the layout\n // tree so the mounted panels (children) participate directly in the\n // parent's flex/grid layout. Without this, the parent's gap/spacing\n // rules treat all mounted panels as a SINGLE child (the slot host),\n // collapsing the spacing between them.\n styles: [`:host { display: contents; }`],\n})\nexport class FormPanelSlotComponent implements OnInit, OnChanges, OnDestroy {\n /** The entity name (e.g., `\"MJ: Content Sources\"`) being edited. */\n @Input() Entity!: string;\n /** Which slot position this host represents. */\n @Input() Slot!: FormPanelSlot;\n /** The record being edited — threaded through to every mounted panel. */\n @Input() Record!: BaseEntity;\n /** The host form component — used by panels for EditMode + dirty notifications. */\n @Input() FormComponent!: BaseFormComponent;\n /** Optional form context — same shape collapsible-panel chrome expects. */\n @Input() FormContext?: FormContext;\n\n @ViewChild('anchor', { read: ViewContainerRef, static: true })\n private anchor!: ViewContainerRef;\n\n private mounted: ComponentRef<BaseFormPanel>[] = [];\n private readonly destroy$ = new Subject<void>();\n private registeredSlot: FormPanelSlot | null = null;\n /** Tracks synchronous re-entry depth into remount() so a future refactor\n * that reintroduces a remount loop surfaces loudly instead of freezing. */\n private remountDepth = 0;\n\n constructor(@Optional() private readonly coordinator?: FormSlotCoordinator) {}\n\n ngOnInit(): void {\n if (this.Slot && this.coordinator) {\n this.coordinator.registerSlot(this.Slot);\n this.registeredSlot = this.Slot;\n // Re-mount whenever the coordinator's known-slot set changes —\n // a panel that was previously orphaned to us might now have a\n // better slot available (or vice versa).\n this.coordinator.changes\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => this.remount());\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {\n // Inputs still being wired — wait for the next pass.\n return;\n }\n\n // Only remount when something that affects WHICH panels to mount has\n // changed. FormContext only flows through to mounted panels — pushing\n // it to the existing panels avoids an unmount/remount cycle.\n //\n // Critical for performance: the host form often reconstructs\n // formContext on every CD pass. Treating that as a structural change\n // would loop (remount → detectChanges → another CD → new FormContext\n // → remount again).\n const meaningfulKeys = ['Entity', 'Slot', 'Record', 'FormComponent'];\n const meaningfulChange = meaningfulKeys.some(k => changes[k]?.currentValue !== changes[k]?.previousValue);\n if (!meaningfulChange && changes['FormContext'] && this.mounted.length > 0) {\n for (const ref of this.mounted) {\n ref.instance.FormContext = this.FormContext;\n }\n return;\n }\n if (!meaningfulChange && this.mounted.length > 0) {\n return;\n }\n this.remount();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n if (this.coordinator && this.registeredSlot) {\n this.coordinator.deregisterSlot(this.registeredSlot);\n this.registeredSlot = null;\n }\n this.unmountAll();\n }\n\n private remount(): void {\n this.remountDepth++;\n if (this.remountDepth > 5) {\n console.error(`[mj-form-panel-slot] LOOP DETECTED — remount depth ${this.remountDepth} on slot=${this.Slot}. Bailing.`);\n this.remountDepth--;\n return;\n }\n this.unmountAll();\n\n // 1. Direct matches: panels registered for THIS slot.\n const direct = this.findRegistrations(this.Slot);\n\n // 2. Orphans: panels registered for some OTHER slot whose preferred\n // slot doesn't exist in this form, AND this slot host is the\n // coordinator-resolved fallback for them. Without a coordinator\n // (no parent container), no orphan handling — panels just bind\n // to their literal slot.\n const orphans = this.findOrphans();\n\n const all = [...direct, ...orphans];\n if (all.length === 0) {\n this.remountDepth--;\n return;\n }\n\n // Sort: higher sortKey first, then higher Priority, then registration order.\n all.sort((a, b) => {\n const aSort = (a.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n const bSort = (b.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n if (aSort !== bSort) return bSort - aSort;\n return b.Priority - a.Priority;\n });\n\n for (const reg of all) {\n try {\n const ctor = reg.SubClass as Type<BaseFormPanel>;\n const ref = this.anchor.createComponent(ctor);\n ref.instance.Record = this.Record;\n ref.instance.FormComponent = this.FormComponent;\n if (this.FormContext) ref.instance.FormContext = this.FormContext;\n // No detectChanges() — Angular's normal CD pass picks the new\n // component up. Calling detectChanges() synchronously inside\n // an ongoing CD cycle (which is when ngOnChanges → remount\n // fires) can re-enter and cause an infinite loop if anything\n // upstream is reconstructing inputs on each CD pass.\n this.mounted.push(ref);\n } catch (e) {\n LogError(`[mj-form-panel-slot] Failed to mount panel for ${this.Entity}:${this.Slot}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n this.remountDepth--;\n }\n\n /** Find registrations whose metadata declares this exact entity + slot. */\n private findRegistrations(slot: FormPanelSlot): ClassRegistration[] {\n return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return meta.entity === this.Entity && meta.slot === slot;\n },\n );\n }\n\n /**\n * Find panels registered for a different slot that should mount HERE\n * because their preferred slot isn't present and this slot is the next\n * existing one in the fallback chain.\n *\n * Only meaningful when we have a coordinator (i.e., we're inside an\n * `<mj-record-form-container>`).\n */\n private findOrphans(): ClassRegistration[] {\n if (!this.coordinator) return [];\n\n const allForEntity = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return meta.entity === this.Entity && meta.slot != null && meta.slot !== this.Slot;\n },\n );\n\n return allForEntity.filter(reg => {\n const meta = reg.Metadata as FormPanelRegistrationMetadata | undefined;\n if (!meta?.slot) return false;\n const resolved = this.coordinator!.resolveSlot(meta.slot);\n return resolved === this.Slot;\n });\n }\n\n private unmountAll(): void {\n this.anchor.clear();\n this.mounted = [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"form-panel-slot.component.js","sourceRoot":"","sources":["../../../src/lib/panel-slot/form-panel-slot.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ComponentRef,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n SimpleChanges,\n Type,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\nimport { BaseEntity, LogError } from '@memberjunction/core';\nimport { ClassRegistration, MJGlobal } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { FormContext } from '../types/form-types';\nimport { BaseFormPanel, FormPanelRegistrationMetadata, FormPanelSlot } from './base-form-panel';\nimport { FormSlotCoordinator } from './form-slot-coordinator.service';\n\n/**\n * `<mj-form-panel-slot>` — dynamic slot host that discovers and mounts every\n * registered `BaseFormPanel` whose metadata matches the slot's `Entity` +\n * `Slot` inputs. Generated entity-form HTML emits one of these at each\n * well-known slot position so consumers can extend the form WITHOUT replacing\n * it via the custom-form override pattern.\n *\n * **Discovery**: uses `ClassFactory.GetAllRegistrationsByMetadata(BaseFormPanel, ...)`\n * to find every panel registered with metadata `{ entity, slot }` matching this\n * slot. Registrations without a metadata bag (or without these required keys)\n * are silently ignored so legacy registrations don't accidentally bind.\n *\n * **Ordering**: registrations are sorted by `metadata.sortKey` descending —\n * higher number renders first within the slot. Use ranges (100/50/10) so future\n * panels can wedge in without renumbering. Ties fall back to the\n * `ClassRegistration.Priority` field (also descending), and finally to\n * registration order.\n *\n * **Fallback chain** (see `FormSlotCoordinator`): each slot host registers\n * with the per-form coordinator and additionally renders panels whose\n * REGISTERED slot is missing in the current form, when this host happens to\n * be the next-existing slot down the chain. The container template guarantees\n * an `after-everything` slot host, so fallback always terminates somewhere.\n * Downstream consumers running an older form template (no CodeGen rerun\n * yet) get every panel rendered at the bottom; consumers on the new template\n * get them in the preferred position.\n *\n * **Re-mount**: any input change OR a coordinator change (another slot\n * registers/deregisters) triggers a re-render so fallback assignments stay\n * correct as slots come and go.\n */\n@Component({\n standalone: false,\n selector: 'mj-form-panel-slot',\n template: `<ng-container #anchor></ng-container>`,\n // `display: contents` makes the host element disappear from the layout\n // tree so the mounted panels (children) participate directly in the\n // parent's flex/grid layout. Without this, the parent's gap/spacing\n // rules treat all mounted panels as a SINGLE child (the slot host),\n // collapsing the spacing between them.\n styles: [`:host { display: contents; }`],\n})\nexport class FormPanelSlotComponent implements OnInit, OnChanges, OnDestroy {\n /** The entity name (e.g., `\"MJ: Content Sources\"`) being edited. */\n @Input() Entity!: string;\n /** Which slot position this host represents. */\n @Input() Slot!: FormPanelSlot;\n /** The record being edited — threaded through to every mounted panel. */\n @Input() Record!: BaseEntity;\n /** The host form component — used by panels for EditMode + dirty notifications. */\n @Input() FormComponent!: BaseFormComponent;\n /** Optional form context — same shape collapsible-panel chrome expects. */\n @Input() FormContext?: FormContext;\n\n @ViewChild('anchor', { read: ViewContainerRef, static: true })\n private anchor!: ViewContainerRef;\n\n private mounted: ComponentRef<BaseFormPanel>[] = [];\n private readonly destroy$ = new Subject<void>();\n private registeredSlot: FormPanelSlot | null = null;\n /** Tracks synchronous re-entry depth into remount() so a future refactor\n * that reintroduces a remount loop surfaces loudly instead of freezing. */\n private remountDepth = 0;\n\n constructor(@Optional() private readonly coordinator?: FormSlotCoordinator) {}\n\n ngOnInit(): void {\n if (this.Slot && this.coordinator) {\n this.coordinator.registerSlot(this.Slot);\n this.registeredSlot = this.Slot;\n // Re-mount whenever the coordinator's known-slot set changes —\n // a panel that was previously orphaned to us might now have a\n // better slot available (or vice versa).\n this.coordinator.changes\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => this.remount());\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.Entity || !this.Slot || !this.Record || !this.FormComponent) {\n // Inputs still being wired — wait for the next pass.\n return;\n }\n\n // Only remount when something that affects WHICH panels to mount has\n // changed. FormContext only flows through to mounted panels — pushing\n // it to the existing panels avoids an unmount/remount cycle.\n //\n // Critical for performance: the host form often reconstructs\n // formContext on every CD pass. Treating that as a structural change\n // would loop (remount → detectChanges → another CD → new FormContext\n // → remount again).\n const meaningfulKeys = ['Entity', 'Slot', 'Record', 'FormComponent'];\n const meaningfulChange = meaningfulKeys.some(k => changes[k]?.currentValue !== changes[k]?.previousValue);\n if (!meaningfulChange && changes['FormContext'] && this.mounted.length > 0) {\n for (const ref of this.mounted) {\n ref.instance.FormContext = this.FormContext;\n }\n return;\n }\n if (!meaningfulChange && this.mounted.length > 0) {\n return;\n }\n this.remount();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n if (this.coordinator && this.registeredSlot) {\n this.coordinator.deregisterSlot(this.registeredSlot);\n this.registeredSlot = null;\n }\n this.unmountAll();\n }\n\n private remount(): void {\n this.remountDepth++;\n if (this.remountDepth > 5) {\n console.error(`[mj-form-panel-slot] LOOP DETECTED — remount depth ${this.remountDepth} on slot=${this.Slot}. Bailing.`);\n this.remountDepth--;\n return;\n }\n this.unmountAll();\n\n // 1. Direct matches: panels registered for THIS slot.\n const direct = this.findRegistrations(this.Slot);\n\n // 2. Orphans: panels registered for some OTHER slot whose preferred\n // slot doesn't exist in this form, AND this slot host is the\n // coordinator-resolved fallback for them. Without a coordinator\n // (no parent container), no orphan handling — panels just bind\n // to their literal slot.\n const orphans = this.findOrphans();\n\n const all = [...direct, ...orphans];\n if (all.length === 0) {\n this.remountDepth--;\n return;\n }\n\n // Sort: higher sortKey first, then higher Priority, then registration order.\n all.sort((a, b) => {\n const aSort = (a.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n const bSort = (b.Metadata as FormPanelRegistrationMetadata | undefined)?.sortKey ?? 0;\n if (aSort !== bSort) return bSort - aSort;\n return b.Priority - a.Priority;\n });\n\n for (const reg of all) {\n try {\n const ctor = reg.SubClass as Type<BaseFormPanel>;\n const ref = this.anchor.createComponent(ctor);\n ref.instance.Record = this.Record;\n ref.instance.FormComponent = this.FormComponent;\n if (this.FormContext) ref.instance.FormContext = this.FormContext;\n // No detectChanges() — Angular's normal CD pass picks the new\n // component up. Calling detectChanges() synchronously inside\n // an ongoing CD cycle (which is when ngOnChanges → remount\n // fires) can re-enter and cause an infinite loop if anything\n // upstream is reconstructing inputs on each CD pass.\n this.mounted.push(ref);\n } catch (e) {\n LogError(`[mj-form-panel-slot] Failed to mount panel for ${this.Entity}:${this.Slot}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n this.remountDepth--;\n }\n\n /**\n * Find registrations whose metadata declares this exact entity (or the\n * `'*'` wildcard) + slot. A panel registered with `entity: '*'` is\n * entity-agnostic — it mounts on EVERY entity's form. Such panels are\n * expected to self-hide (render nothing) when they don't apply to the\n * current record, so the cross-cutting registration stays unobtrusive.\n */\n private findRegistrations(slot: FormPanelSlot): ClassRegistration[] {\n return MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot === slot;\n },\n );\n }\n\n /** True when a registration's `entity` matches this slot's entity, or is the `'*'` wildcard. */\n private entityMatches(registeredEntity: string | undefined): boolean {\n return registeredEntity === '*' || registeredEntity === this.Entity;\n }\n\n /**\n * Find panels registered for a different slot that should mount HERE\n * because their preferred slot isn't present and this slot is the next\n * existing one in the fallback chain.\n *\n * Only meaningful when we have a coordinator (i.e., we're inside an\n * `<mj-record-form-container>`).\n */\n private findOrphans(): ClassRegistration[] {\n if (!this.coordinator) return [];\n\n const allForEntity = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(\n BaseFormPanel,\n (metadata) => {\n if (!metadata) return false;\n const meta = metadata as Partial<FormPanelRegistrationMetadata>;\n return this.entityMatches(meta.entity) && meta.slot != null && meta.slot !== this.Slot;\n },\n );\n\n return allForEntity.filter(reg => {\n const meta = reg.Metadata as FormPanelRegistrationMetadata | undefined;\n if (!meta?.slot) return false;\n const resolved = this.coordinator!.resolveSlot(meta.slot);\n return resolved === this.Slot;\n });\n }\n\n private unmountAll(): void {\n this.anchor.clear();\n this.mounted = [];\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-resolver.service.d.ts","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,IAAI,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAY,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;AAG3D;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,qBAAqB,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GACvF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAiB1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBACa,mBAAmB;IAEtB,oBAAoB,CACtB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,cAAc,CAAC;IAc1B;;;;;OAKG;IACG,qBAAqB,CACvB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAInC;;;;;;;;;;;OAWG;IACH,gBAAuB,yBAAyB,yBAAyB;IAEzE;;;OAGG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;;OAMG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAIhE;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAO5C;;;;;OAKG;IACH,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK9C;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;YAMvC,sBAAsB;
|
|
1
|
+
{"version":3,"file":"form-resolver.service.d.ts","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,IAAI,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAY,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;AAG3D;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,qBAAqB,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GACvF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAiB1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBACa,mBAAmB;IAEtB,oBAAoB,CACtB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,cAAc,CAAC;IAc1B;;;;;OAKG;IACG,qBAAqB,CACvB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAInC;;;;;;;;;;;OAWG;IACH,gBAAuB,yBAAyB,yBAAyB;IAEzE;;;OAGG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;;OAMG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAIhE;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAO5C;;;;;OAKG;IACH,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK9C;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;YAMvC,sBAAsB;IA8EpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU;yCAnMT,mBAAmB;6CAAnB,mBAAmB;CAmN/B"}
|
|
@@ -140,6 +140,11 @@ export class FormResolverService {
|
|
|
140
140
|
LogError(`FormResolverService: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
141
141
|
return [];
|
|
142
142
|
}
|
|
143
|
+
// If the engine couldn't load due to permission denial, fall back to
|
|
144
|
+
// the generated form — no overrides available.
|
|
145
|
+
if (InteractiveFormsEngine.Instance.IsPermissionConstrained) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
143
148
|
const userRoleIds = new Set((user.UserRoles ?? []).map(r => r.RoleID).filter((id) => !!id));
|
|
144
149
|
// Filter cached overrides for this (entity, user, roles) tuple.
|
|
145
150
|
// Includes Active + Pending + Inactive (the variant picker shows
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-resolver.service.js","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, Type } from '@angular/core';\nimport { IMetadataProvider, UserInfo, EntityInfo, LogError } from '@memberjunction/core';\nimport { InteractiveFormsEngine } from '@memberjunction/core-entities';\nimport { MJGlobal, UUIDsEqual } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\n\n/**\n * Slim row shape for an `EntityFormOverride` lookup. Resolution doesn't need\n * the full BaseEntity object — `simple` ResultType is faster and avoids a\n * compile-time dependency on the generated entity class. The row is only ever\n * read by the resolver; mutation goes through the generated entity in other\n * code paths (Studio, AI authoring).\n */\nexport interface EntityFormOverrideRow {\n ID: string;\n EntityID: string;\n ComponentID: string;\n Scope: 'User' | 'Role' | 'Global';\n UserID: string | null;\n RoleID: string | null;\n Priority: number;\n Status: 'Active' | 'Inactive' | 'Pending';\n Name?: string;\n Description?: string | null;\n}\n\n/** Resolved form kind for a given (entity, user, roles) tuple. */\nexport type FormResolution =\n | { kind: 'interactive'; override: EntityFormOverrideRow; variants: EntityFormOverrideRow[] }\n | { kind: 'class'; subClass: Type<BaseFormComponent>; variants: EntityFormOverrideRow[] }\n | { kind: 'none'; variants: EntityFormOverrideRow[] };\n\n/**\n * UserInfoEngine setting-key prefix for per-user, per-entity form-variant\n * preferences. Persisted to `MJ: User Settings` so the choice follows the\n * user across browsers and devices — localStorage is intentionally NOT used.\n *\n * Key format: `mj.formVariant.<entityname-lowercased>`.\n *\n * Value is one of:\n * - an `EntityFormOverride.ID` (UUID) → use that specific override\n * - {@link FormResolverService.EXPLICIT_DEFAULT_SENTINEL} → user picked\n * the CodeGen Angular fallback explicitly; resolver skips all overrides\n * - (key absent) → no preference, apply auto-pick rules\n */\nconst VARIANT_SETTING_PREFIX = 'mj.formVariant.';\n\n/**\n * Picks the form to render for an entity record and exposes the full list of\n * applicable variants so the toolbar's variant switcher can offer alternates.\n *\n * Tier order for the **default** pick:\n * 1. `EntityFormOverride` row matching the caller's User/Role/Global scope,\n * Status='Active', ordered by scope tier (User > Role > Global), then\n * Priority DESC, then `__mj_CreatedAt DESC`. First row wins.\n * 2. Existing `ClassFactory.GetRegistration(BaseFormComponent, entityName)` —\n * the @RegisterClass + CodeGen-generated path used today.\n * 3. None — caller surfaces the \"no form registered\" error.\n *\n * **Session selection.** If the user previously chose a non-default variant\n * via the variant switcher and that choice is still applicable + Active, that\n * choice wins over the default. Choice is keyed by entity name in\n * `localStorage`. Honoring this is part of the variant-switcher contract;\n * without it, switching variants would only last for the lifetime of the\n * record-form view.\n *\n * **Performance.** Backed by `InteractiveFormsEngine` (in MJCoreEntities)\n * — overrides are cached in memory with BaseEntity event-driven\n * invalidation, so resolution is an O(N) JS filter against a small\n * set instead of a per-LoadForm RunView round-trip. Cold latency\n * dropped from ~50ms to <1ms after the first load. The engine is\n * lazy-Config'd here on first resolution; users who never open a\n * record pay nothing.\n */\n@Injectable({ providedIn: 'root' })\nexport class FormResolverService {\n\n async ResolveFormForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<FormResolution> {\n const variants = await this.listApplicableVariants(entity, user, provider);\n const active = this.pickActive(entity, variants);\n\n if (active) {\n return { kind: 'interactive', override: active, variants };\n }\n\n const reg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entity.Name);\n return reg\n ? { kind: 'class', subClass: reg.SubClass as Type<BaseFormComponent>, variants }\n : { kind: 'none', variants };\n }\n\n /**\n * Public list-API for the variant switcher UI. Returns all variants\n * applicable to (entity, user). Includes Active, Pending, and Inactive\n * rows so the picker can offer \"switch back to v1.0.0\" alongside the\n * current active variant.\n */\n async ListVariantsForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n return this.listApplicableVariants(entity, user, provider);\n }\n\n /**\n * Sentinel stored in localStorage when the user **explicitly picks the\n * \"Default form\"** option from the variant picker. Distinct from a\n * missing localStorage key (= \"no preference, use auto-pick rules\")\n * and from an override UUID. Without this sentinel, picking \"Default\"\n * cleared the preference and `pickActive` re-applied the auto-pick —\n * which always selects the first Active override, making the\n * CodeGen/Angular fallback unreachable from the UI.\n *\n * Format: a leading `__` makes it visually distinct from a UUID and\n * impossible to collide with one (UUIDs don't contain underscores).\n */\n public static readonly EXPLICIT_DEFAULT_SENTINEL = '__codegen-default__';\n\n /**\n * Build the per-entity setting key. Lowercased so case variants of\n * the entity name collapse onto a single record in `MJ: User Settings`.\n */\n private settingKey(entityName: string): string {\n return VARIANT_SETTING_PREFIX + entityName.toLowerCase();\n }\n\n /**\n * Persist a specific override choice. Pass the override UUID. Writes\n * via `UserInfoEngine.SetSettingDebounced` so rapid successive picks\n * don't hammer the DB. Use {@link SetExplicitDefault} to record \"user\n * wants the CodeGen Angular fallback\" and {@link ClearSelectedVariant}\n * to wipe the preference entirely (revert to auto-pick).\n */\n SetSelectedVariant(entityName: string, overrideID: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(this.settingKey(entityName), overrideID);\n }\n\n /**\n * Record that the user explicitly picked the \"Default form\" row in\n * the picker. `pickActive` reads this sentinel and returns null even\n * when Active overrides exist for the entity, so the form-loading\n * path falls back to CodeGen's `@RegisterClass` lookup.\n */\n SetExplicitDefault(entityName: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(\n this.settingKey(entityName),\n FormResolverService.EXPLICIT_DEFAULT_SENTINEL,\n );\n }\n\n /**\n * Wipe the user's stored preference for this entity. Next load\n * applies the auto-pick rules (first Active override in tier order).\n * Called internally when a saved override ID is no longer valid.\n * Fire-and-forget — the resolver doesn't need to await the delete.\n */\n ClearSelectedVariant(entityName: string): void {\n void UserInfoEngine.Instance.DeleteSetting(this.settingKey(entityName))\n .catch(err => LogError(`FormResolverService.ClearSelectedVariant: ${err instanceof Error ? err.message : String(err)}`));\n }\n\n /**\n * Read the user's previously-saved variant choice for the entity.\n * Synchronous because `UserInfoEngine` keeps the user-settings table\n * in memory after bootstrap. Returns the stored UUID, the explicit-\n * default sentinel, or null when no preference exists.\n */\n GetSelectedVariant(entityName: string): string | null {\n return UserInfoEngine.Instance.GetSetting(this.settingKey(entityName)) ?? null;\n }\n\n // ── internals ────────────────────────────────────────────────────────\n\n private async listApplicableVariants(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n // Lazy-load the form-metadata engine on first call. No-op when\n // already loaded. BaseEngine's event subscription keeps the\n // cache fresh through save / delete / remote-invalidate — we\n // don't need `BypassCache: true` (the previous RunView path's\n // escape hatch) because the engine's invalidation IS that\n // freshness guarantee. See `InteractiveFormsEngine` doc-block.\n try {\n await InteractiveFormsEngine.Instance.Config(false, user, provider);\n } catch (err) {\n LogError(`FormResolverService: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n const userRoleIds = new Set(\n (user.UserRoles ?? []).map(r => r.RoleID).filter((id): id is string => !!id),\n );\n\n // Filter cached overrides for this (entity, user, roles) tuple.\n // Includes Active + Pending + Inactive (the variant picker shows\n // all three; pickActive() filters to Active separately). Replaces\n // a per-LoadForm RunView with an in-memory predicate — sub-ms.\n const applicable = InteractiveFormsEngine.Instance.Overrides.filter(o => {\n if (!o.EntityID || !UUIDsEqual(o.EntityID, entity.ID)) return false;\n if (o.Scope === 'User') return !!o.UserID && o.UserID.toLowerCase() === user.ID.toLowerCase();\n if (o.Scope === 'Role') return !!o.RoleID && userRoleIds.has(o.RoleID);\n if (o.Scope === 'Global') return true;\n return false;\n });\n\n // Sort: User > Role > Global, then Priority DESC (higher beats),\n // then newest first. Matches the prior SQL ORDER BY semantics so\n // pickActive() / the variant switcher see the same order.\n const scopeRank = (s: string | null | undefined): number => {\n if (s === 'User') return 1;\n if (s === 'Role') return 2;\n if (s === 'Global') return 3;\n return 4;\n };\n applicable.sort((a, b) => {\n const sa = scopeRank(a.Scope);\n const sb = scopeRank(b.Scope);\n if (sa !== sb) return sa - sb;\n const pa = a.Priority ?? 0;\n const pb = b.Priority ?? 0;\n if (pa !== pb) return pb - pa; // Priority DESC\n const ca = a.__mj_CreatedAt instanceof Date ? a.__mj_CreatedAt.getTime() : 0;\n const cb = b.__mj_CreatedAt instanceof Date ? b.__mj_CreatedAt.getTime() : 0;\n return cb - ca; // newest first\n });\n\n // Project to the slim row shape the resolver returns. Keeps the\n // contract identical to the old RunView return so callers don't\n // change shape.\n return applicable.map(o => ({\n ID: o.ID,\n EntityID: o.EntityID,\n ComponentID: o.ComponentID,\n Scope: o.Scope as 'User' | 'Role' | 'Global',\n UserID: o.UserID,\n RoleID: o.RoleID,\n Priority: o.Priority,\n Status: o.Status as 'Active' | 'Inactive' | 'Pending',\n Name: o.Name,\n Description: o.Description,\n }));\n }\n\n /**\n * Pick the override that should actually render given the variant list\n * and the user's stored selection (if any).\n *\n * Selection rules:\n * - If the user explicitly picked the \"Default form\" row in the\n * variant picker (sentinel in localStorage) → return null so the\n * form-loading path falls back to CodeGen's `@RegisterClass` lookup.\n * This is what makes the Angular fallback reachable from the UI.\n * - Else if the user has a saved variant ID AND that variant is in\n * the applicable list AND it's Active → use it.\n * - Else → first Active row in tier+priority order (auto-pick).\n * - Else → null (fall back to CodeGen/@RegisterClass path).\n */\n private pickActive(\n entity: EntityInfo,\n variants: EntityFormOverrideRow[],\n ): EntityFormOverrideRow | null {\n const selectedID = this.GetSelectedVariant(entity.Name);\n if (selectedID === FormResolverService.EXPLICIT_DEFAULT_SENTINEL) {\n return null;\n }\n if (selectedID) {\n const sel = variants.find(v => v.Status === 'Active' && UUIDsEqual(v.ID, selectedID));\n if (sel) return sel;\n // Selection no longer valid — wipe it so future loads auto-pick.\n this.ClearSelectedVariant(entity.Name);\n }\n return variants.find(v => v.Status === 'Active') ?? null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"form-resolver.service.js","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, Type } from '@angular/core';\nimport { IMetadataProvider, UserInfo, EntityInfo, LogError } from '@memberjunction/core';\nimport { InteractiveFormsEngine } from '@memberjunction/core-entities';\nimport { MJGlobal, UUIDsEqual } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\n\n/**\n * Slim row shape for an `EntityFormOverride` lookup. Resolution doesn't need\n * the full BaseEntity object — `simple` ResultType is faster and avoids a\n * compile-time dependency on the generated entity class. The row is only ever\n * read by the resolver; mutation goes through the generated entity in other\n * code paths (Studio, AI authoring).\n */\nexport interface EntityFormOverrideRow {\n ID: string;\n EntityID: string;\n ComponentID: string;\n Scope: 'User' | 'Role' | 'Global';\n UserID: string | null;\n RoleID: string | null;\n Priority: number;\n Status: 'Active' | 'Inactive' | 'Pending';\n Name?: string;\n Description?: string | null;\n}\n\n/** Resolved form kind for a given (entity, user, roles) tuple. */\nexport type FormResolution =\n | { kind: 'interactive'; override: EntityFormOverrideRow; variants: EntityFormOverrideRow[] }\n | { kind: 'class'; subClass: Type<BaseFormComponent>; variants: EntityFormOverrideRow[] }\n | { kind: 'none'; variants: EntityFormOverrideRow[] };\n\n/**\n * UserInfoEngine setting-key prefix for per-user, per-entity form-variant\n * preferences. Persisted to `MJ: User Settings` so the choice follows the\n * user across browsers and devices — localStorage is intentionally NOT used.\n *\n * Key format: `mj.formVariant.<entityname-lowercased>`.\n *\n * Value is one of:\n * - an `EntityFormOverride.ID` (UUID) → use that specific override\n * - {@link FormResolverService.EXPLICIT_DEFAULT_SENTINEL} → user picked\n * the CodeGen Angular fallback explicitly; resolver skips all overrides\n * - (key absent) → no preference, apply auto-pick rules\n */\nconst VARIANT_SETTING_PREFIX = 'mj.formVariant.';\n\n/**\n * Picks the form to render for an entity record and exposes the full list of\n * applicable variants so the toolbar's variant switcher can offer alternates.\n *\n * Tier order for the **default** pick:\n * 1. `EntityFormOverride` row matching the caller's User/Role/Global scope,\n * Status='Active', ordered by scope tier (User > Role > Global), then\n * Priority DESC, then `__mj_CreatedAt DESC`. First row wins.\n * 2. Existing `ClassFactory.GetRegistration(BaseFormComponent, entityName)` —\n * the @RegisterClass + CodeGen-generated path used today.\n * 3. None — caller surfaces the \"no form registered\" error.\n *\n * **Session selection.** If the user previously chose a non-default variant\n * via the variant switcher and that choice is still applicable + Active, that\n * choice wins over the default. Choice is keyed by entity name in\n * `localStorage`. Honoring this is part of the variant-switcher contract;\n * without it, switching variants would only last for the lifetime of the\n * record-form view.\n *\n * **Performance.** Backed by `InteractiveFormsEngine` (in MJCoreEntities)\n * — overrides are cached in memory with BaseEntity event-driven\n * invalidation, so resolution is an O(N) JS filter against a small\n * set instead of a per-LoadForm RunView round-trip. Cold latency\n * dropped from ~50ms to <1ms after the first load. The engine is\n * lazy-Config'd here on first resolution; users who never open a\n * record pay nothing.\n */\n@Injectable({ providedIn: 'root' })\nexport class FormResolverService {\n\n async ResolveFormForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<FormResolution> {\n const variants = await this.listApplicableVariants(entity, user, provider);\n const active = this.pickActive(entity, variants);\n\n if (active) {\n return { kind: 'interactive', override: active, variants };\n }\n\n const reg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entity.Name);\n return reg\n ? { kind: 'class', subClass: reg.SubClass as Type<BaseFormComponent>, variants }\n : { kind: 'none', variants };\n }\n\n /**\n * Public list-API for the variant switcher UI. Returns all variants\n * applicable to (entity, user). Includes Active, Pending, and Inactive\n * rows so the picker can offer \"switch back to v1.0.0\" alongside the\n * current active variant.\n */\n async ListVariantsForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n return this.listApplicableVariants(entity, user, provider);\n }\n\n /**\n * Sentinel stored in localStorage when the user **explicitly picks the\n * \"Default form\"** option from the variant picker. Distinct from a\n * missing localStorage key (= \"no preference, use auto-pick rules\")\n * and from an override UUID. Without this sentinel, picking \"Default\"\n * cleared the preference and `pickActive` re-applied the auto-pick —\n * which always selects the first Active override, making the\n * CodeGen/Angular fallback unreachable from the UI.\n *\n * Format: a leading `__` makes it visually distinct from a UUID and\n * impossible to collide with one (UUIDs don't contain underscores).\n */\n public static readonly EXPLICIT_DEFAULT_SENTINEL = '__codegen-default__';\n\n /**\n * Build the per-entity setting key. Lowercased so case variants of\n * the entity name collapse onto a single record in `MJ: User Settings`.\n */\n private settingKey(entityName: string): string {\n return VARIANT_SETTING_PREFIX + entityName.toLowerCase();\n }\n\n /**\n * Persist a specific override choice. Pass the override UUID. Writes\n * via `UserInfoEngine.SetSettingDebounced` so rapid successive picks\n * don't hammer the DB. Use {@link SetExplicitDefault} to record \"user\n * wants the CodeGen Angular fallback\" and {@link ClearSelectedVariant}\n * to wipe the preference entirely (revert to auto-pick).\n */\n SetSelectedVariant(entityName: string, overrideID: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(this.settingKey(entityName), overrideID);\n }\n\n /**\n * Record that the user explicitly picked the \"Default form\" row in\n * the picker. `pickActive` reads this sentinel and returns null even\n * when Active overrides exist for the entity, so the form-loading\n * path falls back to CodeGen's `@RegisterClass` lookup.\n */\n SetExplicitDefault(entityName: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(\n this.settingKey(entityName),\n FormResolverService.EXPLICIT_DEFAULT_SENTINEL,\n );\n }\n\n /**\n * Wipe the user's stored preference for this entity. Next load\n * applies the auto-pick rules (first Active override in tier order).\n * Called internally when a saved override ID is no longer valid.\n * Fire-and-forget — the resolver doesn't need to await the delete.\n */\n ClearSelectedVariant(entityName: string): void {\n void UserInfoEngine.Instance.DeleteSetting(this.settingKey(entityName))\n .catch(err => LogError(`FormResolverService.ClearSelectedVariant: ${err instanceof Error ? err.message : String(err)}`));\n }\n\n /**\n * Read the user's previously-saved variant choice for the entity.\n * Synchronous because `UserInfoEngine` keeps the user-settings table\n * in memory after bootstrap. Returns the stored UUID, the explicit-\n * default sentinel, or null when no preference exists.\n */\n GetSelectedVariant(entityName: string): string | null {\n return UserInfoEngine.Instance.GetSetting(this.settingKey(entityName)) ?? null;\n }\n\n // ── internals ────────────────────────────────────────────────────────\n\n private async listApplicableVariants(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n // Lazy-load the form-metadata engine on first call. No-op when\n // already loaded. BaseEngine's event subscription keeps the\n // cache fresh through save / delete / remote-invalidate — we\n // don't need `BypassCache: true` (the previous RunView path's\n // escape hatch) because the engine's invalidation IS that\n // freshness guarantee. See `InteractiveFormsEngine` doc-block.\n try {\n await InteractiveFormsEngine.Instance.Config(false, user, provider);\n } catch (err) {\n LogError(`FormResolverService: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // If the engine couldn't load due to permission denial, fall back to\n // the generated form — no overrides available.\n if (InteractiveFormsEngine.Instance.IsPermissionConstrained) {\n return [];\n }\n\n const userRoleIds = new Set(\n (user.UserRoles ?? []).map(r => r.RoleID).filter((id): id is string => !!id),\n );\n\n // Filter cached overrides for this (entity, user, roles) tuple.\n // Includes Active + Pending + Inactive (the variant picker shows\n // all three; pickActive() filters to Active separately). Replaces\n // a per-LoadForm RunView with an in-memory predicate — sub-ms.\n const applicable = InteractiveFormsEngine.Instance.Overrides.filter(o => {\n if (!o.EntityID || !UUIDsEqual(o.EntityID, entity.ID)) return false;\n if (o.Scope === 'User') return !!o.UserID && o.UserID.toLowerCase() === user.ID.toLowerCase();\n if (o.Scope === 'Role') return !!o.RoleID && userRoleIds.has(o.RoleID);\n if (o.Scope === 'Global') return true;\n return false;\n });\n\n // Sort: User > Role > Global, then Priority DESC (higher beats),\n // then newest first. Matches the prior SQL ORDER BY semantics so\n // pickActive() / the variant switcher see the same order.\n const scopeRank = (s: string | null | undefined): number => {\n if (s === 'User') return 1;\n if (s === 'Role') return 2;\n if (s === 'Global') return 3;\n return 4;\n };\n applicable.sort((a, b) => {\n const sa = scopeRank(a.Scope);\n const sb = scopeRank(b.Scope);\n if (sa !== sb) return sa - sb;\n const pa = a.Priority ?? 0;\n const pb = b.Priority ?? 0;\n if (pa !== pb) return pb - pa; // Priority DESC\n const ca = a.__mj_CreatedAt instanceof Date ? a.__mj_CreatedAt.getTime() : 0;\n const cb = b.__mj_CreatedAt instanceof Date ? b.__mj_CreatedAt.getTime() : 0;\n return cb - ca; // newest first\n });\n\n // Project to the slim row shape the resolver returns. Keeps the\n // contract identical to the old RunView return so callers don't\n // change shape.\n return applicable.map(o => ({\n ID: o.ID,\n EntityID: o.EntityID,\n ComponentID: o.ComponentID,\n Scope: o.Scope as 'User' | 'Role' | 'Global',\n UserID: o.UserID,\n RoleID: o.RoleID,\n Priority: o.Priority,\n Status: o.Status as 'Active' | 'Inactive' | 'Pending',\n Name: o.Name,\n Description: o.Description,\n }));\n }\n\n /**\n * Pick the override that should actually render given the variant list\n * and the user's stored selection (if any).\n *\n * Selection rules:\n * - If the user explicitly picked the \"Default form\" row in the\n * variant picker (sentinel in localStorage) → return null so the\n * form-loading path falls back to CodeGen's `@RegisterClass` lookup.\n * This is what makes the Angular fallback reachable from the UI.\n * - Else if the user has a saved variant ID AND that variant is in\n * the applicable list AND it's Active → use it.\n * - Else → first Active row in tier+priority order (auto-pick).\n * - Else → null (fall back to CodeGen/@RegisterClass path).\n */\n private pickActive(\n entity: EntityInfo,\n variants: EntityFormOverrideRow[],\n ): EntityFormOverrideRow | null {\n const selectedID = this.GetSelectedVariant(entity.Name);\n if (selectedID === FormResolverService.EXPLICIT_DEFAULT_SENTINEL) {\n return null;\n }\n if (selectedID) {\n const sel = variants.find(v => v.Status === 'Active' && UUIDsEqual(v.ID, selectedID));\n if (sel) return sel;\n // Selection no longer valid — wipe it so future loads auto-pick.\n this.ClearSelectedVariant(entity.Name);\n }\n return variants.find(v => v.Status === 'Active') ?? null;\n }\n}\n"]}
|
package/dist/module.d.ts
CHANGED
|
@@ -13,14 +13,15 @@ import * as i11 from "./lib/interactive-form/interactive-form.component";
|
|
|
13
13
|
import * as i12 from "./lib/host/entity-form-host.component";
|
|
14
14
|
import * as i13 from "@angular/common";
|
|
15
15
|
import * as i14 from "@angular/forms";
|
|
16
|
-
import * as i15 from "@memberjunction/ng-
|
|
17
|
-
import * as i16 from "@memberjunction/ng-
|
|
18
|
-
import * as i17 from "@memberjunction/ng-
|
|
19
|
-
import * as i18 from "@memberjunction/ng-
|
|
20
|
-
import * as i19 from "@memberjunction/ng-
|
|
21
|
-
import * as i20 from "@memberjunction/ng-
|
|
22
|
-
import * as i21 from "@memberjunction/ng-
|
|
23
|
-
import * as i22 from "@memberjunction/ng-
|
|
16
|
+
import * as i15 from "@memberjunction/ng-ui-components";
|
|
17
|
+
import * as i16 from "@memberjunction/ng-record-changes";
|
|
18
|
+
import * as i17 from "@memberjunction/ng-list-management";
|
|
19
|
+
import * as i18 from "@memberjunction/ng-entity-viewer";
|
|
20
|
+
import * as i19 from "@memberjunction/ng-record-tags";
|
|
21
|
+
import * as i20 from "@memberjunction/ng-react";
|
|
22
|
+
import * as i21 from "@memberjunction/ng-markdown";
|
|
23
|
+
import * as i22 from "@memberjunction/ng-code-editor";
|
|
24
|
+
import * as i23 from "@memberjunction/ng-shared-generic";
|
|
24
25
|
/**
|
|
25
26
|
* BaseFormsModule - Form components and base classes for rendering and editing MemberJunction entity records.
|
|
26
27
|
*
|
|
@@ -42,7 +43,7 @@ import * as i22 from "@memberjunction/ng-shared-generic";
|
|
|
42
43
|
*/
|
|
43
44
|
export declare class BaseFormsModule {
|
|
44
45
|
static ɵfac: i0.ɵɵFactoryDeclaration<BaseFormsModule, never>;
|
|
45
|
-
static ɵmod: i0.ɵɵNgModuleDeclaration<BaseFormsModule, [typeof i1.MjFormToolbarComponent, typeof i2.MjFormFieldComponent, typeof i3.MjCollapsiblePanelComponent, typeof i4.MjRecordFormContainerComponent, typeof i5.MjSectionManagerComponent, typeof i6.SectionLoaderComponent, typeof i7.ExplorerEntityDataGridComponent, typeof i8.MjIsaRelatedCardComponent, typeof i9.MjIsaRelatedPanelComponent, typeof i10.FormPanelSlotComponent, typeof i11.InteractiveFormComponent, typeof i12.MjEntityFormHostComponent], [typeof i13.CommonModule, typeof i14.FormsModule, typeof i15.
|
|
46
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<BaseFormsModule, [typeof i1.MjFormToolbarComponent, typeof i2.MjFormFieldComponent, typeof i3.MjCollapsiblePanelComponent, typeof i4.MjRecordFormContainerComponent, typeof i5.MjSectionManagerComponent, typeof i6.SectionLoaderComponent, typeof i7.ExplorerEntityDataGridComponent, typeof i8.MjIsaRelatedCardComponent, typeof i9.MjIsaRelatedPanelComponent, typeof i10.FormPanelSlotComponent, typeof i11.InteractiveFormComponent, typeof i12.MjEntityFormHostComponent], [typeof i13.CommonModule, typeof i14.FormsModule, typeof i15.MJEmptyStateComponent, typeof i15.MJAlertComponent, typeof i16.RecordChangesModule, typeof i17.ListManagementModule, typeof i18.EntityViewerModule, typeof i19.RecordTagsModule, typeof i20.MJReactModule, typeof i21.MarkdownModule, typeof i22.CodeEditorModule, typeof i23.MJSafeRichHtmlPipe, typeof i23.SharedGenericModule], [typeof i1.MjFormToolbarComponent, typeof i2.MjFormFieldComponent, typeof i3.MjCollapsiblePanelComponent, typeof i4.MjRecordFormContainerComponent, typeof i5.MjSectionManagerComponent, typeof i6.SectionLoaderComponent, typeof i7.ExplorerEntityDataGridComponent, typeof i8.MjIsaRelatedCardComponent, typeof i9.MjIsaRelatedPanelComponent, typeof i10.FormPanelSlotComponent, typeof i11.InteractiveFormComponent, typeof i12.MjEntityFormHostComponent]>;
|
|
46
47
|
static ɵinj: i0.ɵɵInjectorDeclaration<BaseFormsModule>;
|
|
47
48
|
}
|
|
48
49
|
//# sourceMappingURL=module.d.ts.map
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBA6Ca,eAAe;yCAAf,eAAe;0CAAf,eAAe;0CAAf,eAAe;CAAI"}
|
package/dist/module.js
CHANGED
|
@@ -19,6 +19,7 @@ import { MJReactModule } from '@memberjunction/ng-react';
|
|
|
19
19
|
import { MarkdownModule } from '@memberjunction/ng-markdown';
|
|
20
20
|
import { CodeEditorModule } from '@memberjunction/ng-code-editor';
|
|
21
21
|
import { MJSafeRichHtmlPipe, SharedGenericModule } from '@memberjunction/ng-shared-generic';
|
|
22
|
+
import { MJEmptyStateComponent, MJAlertComponent } from '@memberjunction/ng-ui-components';
|
|
22
23
|
import { InteractiveFormComponent } from './lib/interactive-form/interactive-form.component';
|
|
23
24
|
import { MjEntityFormHostComponent } from './lib/host/entity-form-host.component';
|
|
24
25
|
import * as i0 from "@angular/core";
|
|
@@ -46,6 +47,8 @@ export class BaseFormsModule {
|
|
|
46
47
|
static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: BaseFormsModule });
|
|
47
48
|
static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [CommonModule,
|
|
48
49
|
FormsModule,
|
|
50
|
+
MJEmptyStateComponent,
|
|
51
|
+
MJAlertComponent,
|
|
49
52
|
RecordChangesModule,
|
|
50
53
|
ListManagementModule,
|
|
51
54
|
EntityViewerModule,
|
|
@@ -75,6 +78,8 @@ export class BaseFormsModule {
|
|
|
75
78
|
imports: [
|
|
76
79
|
CommonModule,
|
|
77
80
|
FormsModule,
|
|
81
|
+
MJEmptyStateComponent,
|
|
82
|
+
MJAlertComponent,
|
|
78
83
|
RecordChangesModule,
|
|
79
84
|
ListManagementModule,
|
|
80
85
|
EntityViewerModule,
|
|
@@ -114,6 +119,8 @@ export class BaseFormsModule {
|
|
|
114
119
|
InteractiveFormComponent,
|
|
115
120
|
MjEntityFormHostComponent], imports: [CommonModule,
|
|
116
121
|
FormsModule,
|
|
122
|
+
MJEmptyStateComponent,
|
|
123
|
+
MJAlertComponent,
|
|
117
124
|
RecordChangesModule,
|
|
118
125
|
ListManagementModule,
|
|
119
126
|
EntityViewerModule,
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,EAAE,yBAAyB,EAAE,MAAM,oDAAoD,CAAC;AAC/F,OAAO,EAAE,0BAA0B,EAAE,MAAM,qDAAqD,CAAC;AACjG,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;;AAElF;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { MjFormToolbarComponent } from './lib/toolbar/form-toolbar.component';\nimport { MjFormFieldComponent } from './lib/field/form-field.component';\nimport { MjCollapsiblePanelComponent } from './lib/panel/collapsible-panel.component';\nimport { MjRecordFormContainerComponent } from './lib/container/record-form-container.component';\nimport { MjSectionManagerComponent } from './lib/section-manager/section-manager.component';\nimport { SectionLoaderComponent } from './lib/section-loader-component';\nimport { ExplorerEntityDataGridComponent } from './lib/explorer-entity-data-grid.component';\nimport { MjIsaRelatedCardComponent } from './lib/isa-related-panel/isa-related-card.component';\nimport { MjIsaRelatedPanelComponent } from './lib/isa-related-panel/isa-related-panel.component';\nimport { FormPanelSlotComponent } from './lib/panel-slot/form-panel-slot.component';\nimport { RecordChangesModule } from '@memberjunction/ng-record-changes';\nimport { ListManagementModule } from '@memberjunction/ng-list-management';\nimport { EntityViewerModule } from '@memberjunction/ng-entity-viewer';\nimport { RecordTagsModule } from '@memberjunction/ng-record-tags';\nimport { MJReactModule } from '@memberjunction/ng-react';\nimport { MarkdownModule } from '@memberjunction/ng-markdown';\nimport { CodeEditorModule } from '@memberjunction/ng-code-editor';\nimport { MJSafeRichHtmlPipe, SharedGenericModule } from '@memberjunction/ng-shared-generic';\nimport { MJEmptyStateComponent, MJAlertComponent } from '@memberjunction/ng-ui-components';\nimport { InteractiveFormComponent } from './lib/interactive-form/interactive-form.component';\nimport { MjEntityFormHostComponent } from './lib/host/entity-form-host.component';\n\n/**\n * BaseFormsModule - Form components and base classes for rendering and editing MemberJunction entity records.\n *\n * Provides:\n * - **MjRecordFormContainerComponent** (`<mj-record-form-container>`): Top-level container\n * with sticky toolbar, section state management, and content slots.\n * - **MjFormToolbarComponent** (`<mj-form-toolbar>`): Configurable toolbar with read/edit mode\n * actions, IS-A hierarchy breadcrumb, section controls, and inline delete dialog.\n * - **MjCollapsiblePanelComponent** (`<mj-collapsible-panel>`): Collapsible section with\n * drag-to-reorder, search filtering, and inherited/related variants.\n * - **MjFormFieldComponent** (`<mj-form-field>`): Entity field renderer with clean read-only\n * display and modern edit-mode inputs (native HTML with custom select and autocomplete).\n * - **SectionLoaderComponent** (`<mj-form-section>`): Dynamic section loader via ClassFactory.\n * - **ExplorerEntityDataGridComponent** (`<mj-explorer-entity-data-grid>`): Data grid wrapper\n * for related entity sections with Navigate event support.\n *\n * All navigation actions are emitted as events via {@link FormNavigationEvent}.\n * The host application subscribes and maps to its own routing system.\n */\n@NgModule({\n declarations: [\n MjFormToolbarComponent,\n MjFormFieldComponent,\n MjCollapsiblePanelComponent,\n MjRecordFormContainerComponent,\n MjSectionManagerComponent,\n SectionLoaderComponent,\n ExplorerEntityDataGridComponent,\n MjIsaRelatedCardComponent,\n MjIsaRelatedPanelComponent,\n FormPanelSlotComponent,\n InteractiveFormComponent,\n MjEntityFormHostComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n MJEmptyStateComponent,\n MJAlertComponent,\n RecordChangesModule,\n ListManagementModule,\n EntityViewerModule,\n RecordTagsModule,\n MJReactModule,\n MarkdownModule,\n CodeEditorModule,\n MJSafeRichHtmlPipe,\n SharedGenericModule\n ],\n exports: [\n MjFormToolbarComponent,\n MjFormFieldComponent,\n MjCollapsiblePanelComponent,\n MjRecordFormContainerComponent,\n MjSectionManagerComponent,\n SectionLoaderComponent,\n ExplorerEntityDataGridComponent,\n MjIsaRelatedCardComponent,\n MjIsaRelatedPanelComponent,\n FormPanelSlotComponent,\n InteractiveFormComponent,\n MjEntityFormHostComponent\n ]\n})\nexport class BaseFormsModule { }\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-base-forms",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.44.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",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"license": "ISC",
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@angular/compiler": "21.1.3",
|
|
28
|
-
"@angular/compiler-cli": "21.1.3"
|
|
28
|
+
"@angular/compiler-cli": "21.1.3",
|
|
29
|
+
"@memberjunction/ng-test-utils": "5.44.0",
|
|
30
|
+
"vitest": "^4.0.18"
|
|
29
31
|
},
|
|
30
32
|
"peerDependencies": {
|
|
31
33
|
"@angular/animations": "21.1.3",
|
|
@@ -36,21 +38,21 @@
|
|
|
36
38
|
"primeng": "21.1.1"
|
|
37
39
|
},
|
|
38
40
|
"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.
|
|
41
|
+
"@memberjunction/core": "5.44.0",
|
|
42
|
+
"@memberjunction/core-entities": "5.44.0",
|
|
43
|
+
"@memberjunction/global": "5.44.0",
|
|
44
|
+
"@memberjunction/interactive-component-types": "5.44.0",
|
|
45
|
+
"@memberjunction/ng-base-types": "5.44.0",
|
|
46
|
+
"@memberjunction/ng-code-editor": "5.44.0",
|
|
47
|
+
"@memberjunction/ng-entity-viewer": "5.44.0",
|
|
48
|
+
"@memberjunction/ng-list-management": "5.44.0",
|
|
49
|
+
"@memberjunction/ng-markdown": "5.44.0",
|
|
50
|
+
"@memberjunction/ng-notifications": "5.44.0",
|
|
51
|
+
"@memberjunction/ng-react": "5.44.0",
|
|
52
|
+
"@memberjunction/ng-record-changes": "5.44.0",
|
|
53
|
+
"@memberjunction/ng-record-tags": "5.44.0",
|
|
54
|
+
"@memberjunction/ng-shared-generic": "5.44.0",
|
|
55
|
+
"@memberjunction/ng-ui-components": "5.44.0",
|
|
54
56
|
"rxjs": "^7.8.2",
|
|
55
57
|
"tslib": "^2.8.1"
|
|
56
58
|
},
|