@memberjunction/ng-explorer-core 6.2.0-edge.5 → 6.2.0-edge.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +7 -2
  2. package/dist/app-routing.module.d.ts.map +1 -1
  3. package/dist/app-routing.module.js +12 -4
  4. package/dist/app-routing.module.js.map +1 -1
  5. package/dist/generated/lazy-feature-config.d.ts +1 -1
  6. package/dist/generated/lazy-feature-config.d.ts.map +1 -1
  7. package/dist/generated/lazy-feature-config.js +4 -2
  8. package/dist/generated/lazy-feature-config.js.map +1 -1
  9. package/dist/lib/resource-wrappers/record-form-mode.d.ts +44 -0
  10. package/dist/lib/resource-wrappers/record-form-mode.d.ts.map +1 -0
  11. package/dist/lib/resource-wrappers/record-form-mode.js +47 -0
  12. package/dist/lib/resource-wrappers/record-form-mode.js.map +1 -0
  13. package/dist/lib/resource-wrappers/record-resource.component.d.ts +56 -1
  14. package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
  15. package/dist/lib/resource-wrappers/record-resource.component.js +124 -9
  16. package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
  17. package/dist/lib/resource-wrappers/standard-form-create.d.ts +4 -0
  18. package/dist/lib/resource-wrappers/standard-form-create.d.ts.map +1 -0
  19. package/dist/lib/resource-wrappers/standard-form-create.js +6 -0
  20. package/dist/lib/resource-wrappers/standard-form-create.js.map +1 -0
  21. package/dist/lib/resource-wrappers/view-resource.component.d.ts +18 -1
  22. package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
  23. package/dist/lib/resource-wrappers/view-resource.component.js +105 -39
  24. package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
  25. package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -1
  26. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +12 -8
  27. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -1
  28. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +6 -0
  29. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
  30. package/dist/lib/shell/components/tabs/component-cache-manager.js +18 -3
  31. package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
  32. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +2 -0
  33. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  34. package/dist/lib/shell/components/tabs/tab-container.component.js +38 -10
  35. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  36. package/dist/lib/shell/shell-chrome-policy.d.ts +6 -0
  37. package/dist/lib/shell/shell-chrome-policy.d.ts.map +1 -1
  38. package/dist/lib/shell/shell-chrome-policy.js +1 -0
  39. package/dist/lib/shell/shell-chrome-policy.js.map +1 -1
  40. package/dist/lib/shell/shell.component.d.ts +23 -0
  41. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  42. package/dist/lib/shell/shell.component.js +283 -71
  43. package/dist/lib/shell/shell.component.js.map +1 -1
  44. package/dist/lib/single-record/single-record.component.d.ts +27 -2
  45. package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
  46. package/dist/lib/single-record/single-record.component.js +54 -8
  47. package/dist/lib/single-record/single-record.component.js.map +1 -1
  48. package/dist/lib/user-notifications/user-notifications.component.d.ts +10 -2
  49. package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
  50. package/dist/lib/user-notifications/user-notifications.component.js +77 -22
  51. package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
  52. package/package.json +57 -57
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter, OnDestroy } from '@angular/core';
2
2
  import { CompositeKey, BaseEntity } from '@memberjunction/core';
3
- import { FormNavigationEvent, FormNotificationEvent } from '@memberjunction/ng-base-forms';
3
+ import { EntityFormMode, FormNavigationEvent, FormNotificationEvent } from '@memberjunction/ng-base-forms';
4
4
  import type { BaseFormComponent, FormCompositionSnapshot } from '@memberjunction/ng-base-forms';
5
5
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
6
6
  import * as i0 from "@angular/core";
@@ -22,6 +22,18 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
22
22
  PrimaryKey: CompositeKey;
23
23
  entityName: string | null;
24
24
  NewRecordValues: string | Record<string, unknown> | null;
25
+ /**
26
+ * `'standard'` asks the host for the CodeGen form even when a custom form is
27
+ * registered (MJ#4755). The mount-time mode; later changes go through
28
+ * {@link SwitchFormMode} so the host's unsaved-work guard applies.
29
+ */
30
+ FormMode: EntityFormMode;
31
+ /**
32
+ * Re-emitted host `FormModeChange`: the mode changed inside the host — the
33
+ * strip, a {@link SwitchFormMode} call, or an interactive-variant pick
34
+ * (which returns to `'default'`).
35
+ */
36
+ FormModeChange: EventEmitter<EntityFormMode>;
25
37
  /** @deprecated Use {@link NewRecordValues}. */
26
38
  set newRecordValues(value: string | Record<string, unknown> | null);
27
39
  /** @deprecated Use {@link NewRecordValues}. */
@@ -68,7 +80,18 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
68
80
  * only route the snapshot has out to Explorer.
69
81
  */
70
82
  CompositionChanged: EventEmitter<FormCompositionSnapshot>;
83
+ /**
84
+ * Emitted when the user clicks "Ask AI" in the record form toolbar.
85
+ * Also requests expansion of the floating chat overlay via ConversationsRuntime.
86
+ */
87
+ AskAIRequested: EventEmitter<void>;
71
88
  private formHost?;
89
+ /**
90
+ * Switch the mounted form between custom and standard (MJ#4755) through the
91
+ * host's guarded switch. False when refused (unsaved work — the host has
92
+ * already raised a warning) or when no host is mounted yet.
93
+ */
94
+ SwitchFormMode(mode: EntityFormMode): boolean;
72
95
  /**
73
96
  * True while the hosted form is in edit mode. The host already exposes the
74
97
  * live form instance, so this is a read, not a new event pipeline.
@@ -83,6 +106,7 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
83
106
  private formPresenter;
84
107
  private recentAccessService;
85
108
  private cdr;
109
+ constructor();
86
110
  /**
87
111
  * True once another user (another session) has deleted the record shown here.
88
112
  * The tab is NOT closed under the user — they may be reading it — so a banner
@@ -116,6 +140,7 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
116
140
  /** @deprecated Use {@link OnSaved}. */
117
141
  onSaved(record: BaseEntity): void;
118
142
  OnEditModeChanged(editing: boolean): void;
143
+ OnAskAIRequested(): void;
119
144
  OnNotification(event: FormNotificationEvent): void;
120
145
  /** @deprecated Use {@link OnNotification}. */
121
146
  onNotification(event: FormNotificationEvent): void;
@@ -124,6 +149,6 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
124
149
  /** @deprecated Use {@link HandleNavigation}. */
125
150
  handleNavigation(event: FormNavigationEvent): void;
126
151
  static ɵfac: i0.ɵɵFactoryDeclaration<SingleRecordComponent, never>;
127
- static ɵcmp: i0.ɵɵComponentDeclaration<SingleRecordComponent, "mj-single-record", never, { "PrimaryKey": { "alias": "PrimaryKey"; "required": false; }; "entityName": { "alias": "entityName"; "required": false; }; "NewRecordValues": { "alias": "NewRecordValues"; "required": false; }; "newRecordValues": { "alias": "newRecordValues"; "required": false; }; }, { "LoadComplete": "LoadComplete"; "loadComplete": "loadComplete"; "RecordSaved": "RecordSaved"; "recordSaved": "recordSaved"; "RecordDismissed": "RecordDismissed"; "recordDismissed": "recordDismissed"; "EditModeChanged": "EditModeChanged"; "CompositionChanged": "CompositionChanged"; }, never, never, false, never>;
152
+ static ɵcmp: i0.ɵɵComponentDeclaration<SingleRecordComponent, "mj-single-record", never, { "PrimaryKey": { "alias": "PrimaryKey"; "required": false; }; "entityName": { "alias": "entityName"; "required": false; }; "NewRecordValues": { "alias": "NewRecordValues"; "required": false; }; "FormMode": { "alias": "FormMode"; "required": false; }; "newRecordValues": { "alias": "newRecordValues"; "required": false; }; }, { "FormModeChange": "FormModeChange"; "LoadComplete": "LoadComplete"; "loadComplete": "loadComplete"; "RecordSaved": "RecordSaved"; "recordSaved": "recordSaved"; "RecordDismissed": "RecordDismissed"; "recordDismissed": "recordDismissed"; "EditModeChanged": "EditModeChanged"; "CompositionChanged": "CompositionChanged"; "AskAIRequested": "AskAIRequested"; }, never, never, false, never>;
128
153
  }
129
154
  //# sourceMappingURL=single-record.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"single-record.component.d.ts","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"single-record.component.d.ts","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  import { ChangeDetectorRef, Component, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core';
2
2
  import { CompositeKey } from '@memberjunction/core';
3
- import { MJFormPresenterService, MjEntityFormHostComponent } from '@memberjunction/ng-base-forms';
3
+ import { MJFormPresenterService, MjEntityFormHostComponent, MjRecordPreviewPopoverService } from '@memberjunction/ng-base-forms';
4
4
  import { GetDeletedRecordFromEvent, NavigationService, RecentAccessService, RecordIdentityMatches, SafeDetectChanges, SharedService } from '@memberjunction/ng-shared';
5
5
  import { MJGlobal } from '@memberjunction/global';
6
6
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
7
+ import { ConversationsRuntime } from '@memberjunction/conversations-runtime';
7
8
  import * as i0 from "@angular/core";
8
9
  import * as i1 from "@memberjunction/ng-base-forms";
9
10
  import * as i2 from "@memberjunction/ng-ui-components";
@@ -32,6 +33,18 @@ export class SingleRecordComponent extends BaseAngularComponent {
32
33
  PrimaryKey = new CompositeKey();
33
34
  entityName = '';
34
35
  NewRecordValues = '';
36
+ /**
37
+ * `'standard'` asks the host for the CodeGen form even when a custom form is
38
+ * registered (MJ#4755). The mount-time mode; later changes go through
39
+ * {@link SwitchFormMode} so the host's unsaved-work guard applies.
40
+ */
41
+ FormMode = 'default';
42
+ /**
43
+ * Re-emitted host `FormModeChange`: the mode changed inside the host — the
44
+ * strip, a {@link SwitchFormMode} call, or an interactive-variant pick
45
+ * (which returns to `'default'`).
46
+ */
47
+ FormModeChange = new EventEmitter();
35
48
  /** @deprecated Use {@link NewRecordValues}. */
36
49
  set newRecordValues(value) {
37
50
  this.NewRecordValues = value;
@@ -82,7 +95,20 @@ export class SingleRecordComponent extends BaseAngularComponent {
82
95
  * only route the snapshot has out to Explorer.
83
96
  */
84
97
  CompositionChanged = new EventEmitter();
98
+ /**
99
+ * Emitted when the user clicks "Ask AI" in the record form toolbar.
100
+ * Also requests expansion of the floating chat overlay via ConversationsRuntime.
101
+ */
102
+ AskAIRequested = new EventEmitter();
85
103
  formHost;
104
+ /**
105
+ * Switch the mounted form between custom and standard (MJ#4755) through the
106
+ * host's guarded switch. False when refused (unsaved work — the host has
107
+ * already raised a warning) or when no host is mounted yet.
108
+ */
109
+ SwitchFormMode(mode) {
110
+ return this.formHost?.SwitchFormMode(mode) ?? false;
111
+ }
86
112
  /**
87
113
  * True while the hosted form is in edit mode. The host already exposes the
88
114
  * live form instance, so this is a read, not a new event pipeline.
@@ -99,6 +125,16 @@ export class SingleRecordComponent extends BaseAngularComponent {
99
125
  formPresenter = inject(MJFormPresenterService);
100
126
  recentAccessService = new RecentAccessService();
101
127
  cdr = inject(ChangeDetectorRef);
128
+ constructor() {
129
+ super();
130
+ if (!MjRecordPreviewPopoverService.FallbackNavigationHandler) {
131
+ MjRecordPreviewPopoverService.FallbackNavigationHandler = (event) => {
132
+ if (event.Kind === 'record') {
133
+ this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });
134
+ }
135
+ };
136
+ }
137
+ }
102
138
  /**
103
139
  * True once another user (another session) has deleted the record shown here.
104
140
  * The tab is NOT closed under the user — they may be reading it — so a banner
@@ -174,6 +210,10 @@ export class SingleRecordComponent extends BaseAngularComponent {
174
210
  OnEditModeChanged(editing) {
175
211
  this.EditModeChanged.emit(editing);
176
212
  }
213
+ OnAskAIRequested() {
214
+ this.AskAIRequested.emit();
215
+ ConversationsRuntime.Instance.Bridge.RequestExpandOverlay();
216
+ }
177
217
  OnNotification(event) {
178
218
  this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);
179
219
  }
@@ -226,32 +266,36 @@ export class SingleRecordComponent extends BaseAngularComponent {
226
266
  handleNavigation(event) {
227
267
  return this.HandleNavigation(event);
228
268
  }
229
- static ɵfac = /*@__PURE__*/ (() => { let ɵSingleRecordComponent_BaseFactory; return function SingleRecordComponent_Factory(__ngFactoryType__) { return (ɵSingleRecordComponent_BaseFactory || (ɵSingleRecordComponent_BaseFactory = i0.ɵɵgetInheritedFactory(SingleRecordComponent)))(__ngFactoryType__ || SingleRecordComponent); }; })();
269
+ static ɵfac = function SingleRecordComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SingleRecordComponent)(); };
230
270
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleRecordComponent, selectors: [["mj-single-record"]], viewQuery: function SingleRecordComponent_Query(rf, ctx) { if (rf & 1) {
231
271
  i0.ɵɵviewQuery(MjEntityFormHostComponent, 5);
232
272
  } if (rf & 2) {
233
273
  let _t;
234
274
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.formHost = _t.first);
235
- } }, inputs: { PrimaryKey: "PrimaryKey", entityName: "entityName", NewRecordValues: "NewRecordValues", newRecordValues: "newRecordValues" }, outputs: { LoadComplete: "LoadComplete", loadComplete: "loadComplete", RecordSaved: "RecordSaved", recordSaved: "recordSaved", RecordDismissed: "RecordDismissed", recordDismissed: "recordDismissed", EditModeChanged: "EditModeChanged", CompositionChanged: "CompositionChanged" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 5, consts: [["Variant", "warning", "Title", "This record was deleted by another user", "Message", "It no longer exists. What you see here is the last copy that was loaded.", 1, "single-record-deleted-banner"], [3, "LoadComplete", "LoadError", "RecordReady", "Saved", "Navigate", "Notification", "EditModeChanged", "Dismissed", "FormCreated", "EntityName", "PrimaryKey", "NewRecordValues", "Provider"], ["actions", "", "mjButton", "", "Variant", "secondary", "Size", "sm", 3, "click"]], template: function SingleRecordComponent_Template(rf, ctx) { if (rf & 1) {
275
+ } }, inputs: { PrimaryKey: "PrimaryKey", entityName: "entityName", NewRecordValues: "NewRecordValues", FormMode: "FormMode", newRecordValues: "newRecordValues" }, outputs: { FormModeChange: "FormModeChange", LoadComplete: "LoadComplete", loadComplete: "loadComplete", RecordSaved: "RecordSaved", recordSaved: "recordSaved", RecordDismissed: "RecordDismissed", recordDismissed: "recordDismissed", EditModeChanged: "EditModeChanged", CompositionChanged: "CompositionChanged", AskAIRequested: "AskAIRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 6, consts: [["Variant", "warning", "Title", "This record was deleted by another user", "Message", "It no longer exists. What you see here is the last copy that was loaded.", 1, "single-record-deleted-banner"], [3, "FormModeChange", "LoadComplete", "LoadError", "RecordReady", "Saved", "Navigate", "Notification", "EditModeChanged", "AskAIRequested", "Dismissed", "FormCreated", "EntityName", "PrimaryKey", "NewRecordValues", "FormMode", "Provider"], ["actions", "", "mjButton", "", "Variant", "secondary", "Size", "sm", 3, "click"]], template: function SingleRecordComponent_Template(rf, ctx) { if (rf & 1) {
236
276
  i0.ɵɵconditionalCreate(0, SingleRecordComponent_Conditional_0_Template, 3, 0, "mj-alert", 0);
237
277
  i0.ɵɵelementStart(1, "mj-entity-form-host", 1);
238
- i0.ɵɵlistener("LoadComplete", function SingleRecordComponent_Template_mj_entity_form_host_LoadComplete_1_listener() { return ctx.onLoadComplete(); })("LoadError", function SingleRecordComponent_Template_mj_entity_form_host_LoadError_1_listener() { return ctx.onLoadComplete(); })("RecordReady", function SingleRecordComponent_Template_mj_entity_form_host_RecordReady_1_listener($event) { return ctx.onRecordReady($event); })("Saved", function SingleRecordComponent_Template_mj_entity_form_host_Saved_1_listener($event) { return ctx.onSaved($event); })("Navigate", function SingleRecordComponent_Template_mj_entity_form_host_Navigate_1_listener($event) { return ctx.handleNavigation($event); })("Notification", function SingleRecordComponent_Template_mj_entity_form_host_Notification_1_listener($event) { return ctx.onNotification($event); })("EditModeChanged", function SingleRecordComponent_Template_mj_entity_form_host_EditModeChanged_1_listener($event) { return ctx.OnEditModeChanged($event); })("Dismissed", function SingleRecordComponent_Template_mj_entity_form_host_Dismissed_1_listener() { return ctx.recordDismissed.emit(); })("FormCreated", function SingleRecordComponent_Template_mj_entity_form_host_FormCreated_1_listener($event) { return ctx.onFormCreated($event); });
278
+ i0.ɵɵlistener("FormModeChange", function SingleRecordComponent_Template_mj_entity_form_host_FormModeChange_1_listener($event) { return ctx.FormModeChange.emit($event); })("LoadComplete", function SingleRecordComponent_Template_mj_entity_form_host_LoadComplete_1_listener() { return ctx.onLoadComplete(); })("LoadError", function SingleRecordComponent_Template_mj_entity_form_host_LoadError_1_listener() { return ctx.onLoadComplete(); })("RecordReady", function SingleRecordComponent_Template_mj_entity_form_host_RecordReady_1_listener($event) { return ctx.onRecordReady($event); })("Saved", function SingleRecordComponent_Template_mj_entity_form_host_Saved_1_listener($event) { return ctx.onSaved($event); })("Navigate", function SingleRecordComponent_Template_mj_entity_form_host_Navigate_1_listener($event) { return ctx.handleNavigation($event); })("Notification", function SingleRecordComponent_Template_mj_entity_form_host_Notification_1_listener($event) { return ctx.onNotification($event); })("EditModeChanged", function SingleRecordComponent_Template_mj_entity_form_host_EditModeChanged_1_listener($event) { return ctx.OnEditModeChanged($event); })("AskAIRequested", function SingleRecordComponent_Template_mj_entity_form_host_AskAIRequested_1_listener() { return ctx.OnAskAIRequested(); })("Dismissed", function SingleRecordComponent_Template_mj_entity_form_host_Dismissed_1_listener() { return ctx.recordDismissed.emit(); })("FormCreated", function SingleRecordComponent_Template_mj_entity_form_host_FormCreated_1_listener($event) { return ctx.onFormCreated($event); });
239
279
  i0.ɵɵelementEnd();
240
280
  } if (rf & 2) {
241
281
  i0.ɵɵconditional(ctx.DeletedRemotely ? 0 : -1);
242
282
  i0.ɵɵadvance();
243
- i0.ɵɵproperty("EntityName", ctx.entityName)("PrimaryKey", ctx.PrimaryKey)("NewRecordValues", ctx.newRecordValues)("Provider", ctx.Provider);
283
+ i0.ɵɵproperty("EntityName", ctx.entityName)("PrimaryKey", ctx.PrimaryKey)("NewRecordValues", ctx.newRecordValues)("FormMode", ctx.FormMode)("Provider", ctx.Provider);
244
284
  } }, dependencies: [i1.MjEntityFormHostComponent, i2.MJButtonDirective, i2.MJAlertComponent], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n}\n\n\nmj-entity-form-host[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.single-record-deleted-banner[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: var(--mj-space-3) var(--mj-space-4) 0;\n}\n\n.single-record-error[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail[_ngcontent-%COMP%] {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}"] });
245
285
  }
246
286
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SingleRecordComponent, [{
247
287
  type: Component,
248
- args: [{ standalone: false, selector: 'mj-single-record', template: "@if (DeletedRemotely) {\n <mj-alert\n class=\"single-record-deleted-banner\"\n Variant=\"warning\"\n Title=\"This record was deleted by another user\"\n Message=\"It no longer exists. What you see here is the last copy that was loaded.\">\n <button actions mjButton Variant=\"secondary\" Size=\"sm\" (click)=\"OnCloseDeletedRecord()\">Close</button>\n </mj-alert>\n}\n<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (EditModeChanged)=\"OnEditModeChanged($event)\"\n (Dismissed)=\"recordDismissed.emit()\"\n (FormCreated)=\"onFormCreated($event)\">\n</mj-entity-form-host>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n}\n\n/* The form fills what the remote-delete banner leaves. */\nmj-entity-form-host {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.single-record-deleted-banner {\n flex: 0 0 auto;\n margin: var(--mj-space-3) var(--mj-space-4) 0;\n}\n\n.single-record-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}\n"] }]
249
- }], null, { PrimaryKey: [{
288
+ args: [{ standalone: false, selector: 'mj-single-record', template: "@if (DeletedRemotely) {\n <mj-alert\n class=\"single-record-deleted-banner\"\n Variant=\"warning\"\n Title=\"This record was deleted by another user\"\n Message=\"It no longer exists. What you see here is the last copy that was loaded.\">\n <button actions mjButton Variant=\"secondary\" Size=\"sm\" (click)=\"OnCloseDeletedRecord()\">Close</button>\n </mj-alert>\n}\n<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [FormMode]=\"FormMode\"\n (FormModeChange)=\"FormModeChange.emit($event)\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (EditModeChanged)=\"OnEditModeChanged($event)\"\n (AskAIRequested)=\"OnAskAIRequested()\"\n (Dismissed)=\"recordDismissed.emit()\"\n (FormCreated)=\"onFormCreated($event)\">\n</mj-entity-form-host>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n}\n\n/* The form fills what the remote-delete banner leaves. */\nmj-entity-form-host {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.single-record-deleted-banner {\n flex: 0 0 auto;\n margin: var(--mj-space-3) var(--mj-space-4) 0;\n}\n\n.single-record-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}\n"] }]
289
+ }], () => [], { PrimaryKey: [{
250
290
  type: Input
251
291
  }], entityName: [{
252
292
  type: Input
253
293
  }], NewRecordValues: [{
254
294
  type: Input
295
+ }], FormMode: [{
296
+ type: Input
297
+ }], FormModeChange: [{
298
+ type: Output
255
299
  }], newRecordValues: [{
256
300
  type: Input
257
301
  }], LoadComplete: [{
@@ -270,9 +314,11 @@ export class SingleRecordComponent extends BaseAngularComponent {
270
314
  type: Output
271
315
  }], CompositionChanged: [{
272
316
  type: Output
317
+ }], AskAIRequested: [{
318
+ type: Output
273
319
  }], formHost: [{
274
320
  type: ViewChild,
275
321
  args: [MjEntityFormHostComponent]
276
322
  }] }); })();
277
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SingleRecordComponent, { className: "SingleRecordComponent", filePath: "src/lib/single-record/single-record.component.ts", lineNumber: 30 }); })();
323
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SingleRecordComponent, { className: "SingleRecordComponent", filePath: "src/lib/single-record/single-record.component.ts", lineNumber: 31 }); })();
278
324
  //# sourceMappingURL=single-record.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, EventEmitter, inject, Input, OnDestroy, Output, ViewChild } from '@angular/core';\nimport { CompositeKey, BaseEntity } from '@memberjunction/core';\nimport { FormNavigationEvent, FormNotificationEvent, MJFormPresenterService, MjEntityFormHostComponent } from '@memberjunction/ng-base-forms';\nimport type { BaseFormComponent, FormCompositionSnapshot } from '@memberjunction/ng-base-forms';\nimport { Subscription } from 'rxjs';\nimport { GetDeletedRecordFromEvent, NavigationService, RecentAccessService, RecordIdentityMatches, SafeDetectChanges, SharedService } from '@memberjunction/ng-shared';\nimport { MJEvent, MJGlobal } from '@memberjunction/global';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n\n/**\n * Explorer-side host for a single entity record in the main tab area.\n *\n * This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`\n * (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving\n * the form (class / custom / interactive override + variants), loading the\n * record, dynamically creating the form, binding it, and tearing it down.\n *\n * SingleRecordComponent's only remaining job is the **Explorer mapping**:\n * translating the host's framework-agnostic events into Explorer services —\n * `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},\n * record loads → {@link RecentAccessService} — none of which belong in a Generic\n * component.\n */\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent implements OnDestroy {\n @Input() public PrimaryKey: CompositeKey = new CompositeKey();\n @Input() public entityName: string | null = '';\n @Input() public NewRecordValues: string | Record<string, unknown> | null = '';\n\n /** @deprecated Use {@link NewRecordValues}. */\n @Input() public set newRecordValues(value: string | Record<string, unknown> | null) {\n this.NewRecordValues = value;\n }\n /** @deprecated Use {@link NewRecordValues}. */\n public get newRecordValues(): string | Record<string, unknown> | null {\n return this.NewRecordValues;\n }\n\n @Output() public LoadComplete: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * @deprecated Use {@link LoadComplete}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (loadComplete) keeps working. Must stay AFTER LoadComplete: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public loadComplete = this.LoadComplete;\n @Output() public RecordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n\n /**\n * @deprecated Use {@link RecordSaved}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (recordSaved) keeps working. Must stay AFTER RecordSaved: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public recordSaved = this.RecordSaved;\n /** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */\n @Output() public RecordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * @deprecated Use {@link RecordDismissed}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (recordDismissed) keeps working. Must stay AFTER RecordDismissed: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public recordDismissed = this.RecordDismissed;\n\n /**\n * Edit mode of the hosted form started (true) or ended (false). Relayed from\n * `<mj-entity-form-host>` so the resource wrapper can tell the shell to\n * promote this tab. {@link IsEditing} remains as the synchronous fallback.\n */\n @Output() public EditModeChanged: EventEmitter<boolean> = new EventEmitter<boolean>();\n\n /**\n * The live form's composition — sections, related grids, contributions, and the slots\n * the form actually emits. Re-emitted on every chrome resolve.\n *\n * The form is created dynamically inside `<mj-entity-form-host>`, so this relay is the\n * only route the snapshot has out to Explorer.\n */\n @Output() public CompositionChanged: EventEmitter<FormCompositionSnapshot> = new EventEmitter<FormCompositionSnapshot>();\n\n @ViewChild(MjEntityFormHostComponent) private formHost?: MjEntityFormHostComponent;\n\n /**\n * True while the hosted form is in edit mode. The host already exposes the\n * live form instance, so this is a read, not a new event pipeline.\n *\n * MJ forms are read-only until the user explicitly clicks Edit, so edit mode\n * is a deliberate gesture and a sound proxy for \"there is work in here worth\n * protecting\" — which is all the preview-tab replacement guard needs.\n */\n public IsEditing(): boolean {\n return this.formHost?.Form?.EditMode === true;\n }\n\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private formPresenter = inject(MJFormPresenterService);\n private recentAccessService = new RecentAccessService();\n private cdr = inject(ChangeDetectorRef);\n\n /**\n * True once another user (another session) has deleted the record shown here.\n * The tab is NOT closed under the user — they may be reading it — so a banner\n * says so and offers to close it. Deletes in this session are handled by the\n * shell, which closes the tab outright.\n */\n public DeletedRemotely = false;\n\n private remoteDeleteSub: Subscription | null = MJGlobal.Instance.GetEventListener()\n .subscribe((event: MJEvent) => this.onGlobalEvent(event));\n\n /** Close this record's tab (the remote-delete banner's Close button). */\n OnCloseDeletedRecord(): void {\n this.RecordDismissed.emit();\n }\n\n private onGlobalEvent(event: MJEvent): void {\n if (this.DeletedRemotely) {\n return;\n }\n const deleted = GetDeletedRecordFromEvent(event);\n if (!deleted?.Remote || !this.PrimaryKey?.HasValue) {\n return;\n }\n if (RecordIdentityMatches(this.entityName, this.PrimaryKey.ToURLSegment(), deleted)) {\n this.DeletedRemotely = true;\n SafeDetectChanges(this.cdr);\n }\n }\n\n /** Unblock the shell's first-resource-load gate (success or error). */\n OnLoadComplete(): void {\n this.LoadComplete.emit();\n }\n\n /** @deprecated Use {@link OnLoadComplete}. */\n onLoadComplete(): void {\n return this.OnLoadComplete();\n }\n\n private compositionSub: Subscription | null = null;\n\n /**\n * Bind to the newly created form's composition stream.\n *\n * The previous subscription is dropped first: the host re-creates the form when the\n * user switches variants, and a retained subscription would let the replaced form\n * keep publishing over the live one.\n */\n onFormCreated(form: BaseFormComponent): void {\n this.compositionSub?.unsubscribe();\n this.compositionSub = form.CompositionChanged.subscribe((snapshot: FormCompositionSnapshot) =>\n this.CompositionChanged.emit(snapshot),\n );\n if (form.CompositionSnapshot) {\n this.CompositionChanged.emit(form.CompositionSnapshot);\n }\n }\n\n ngOnDestroy(): void {\n this.compositionSub?.unsubscribe();\n this.compositionSub = null;\n this.remoteDeleteSub?.unsubscribe();\n this.remoteDeleteSub = null;\n }\n\n /** Log access for existing records once the form's record is ready. */\n OnRecordReady(record: BaseEntity): void {\n if (record?.IsSaved) {\n this.recentAccessService.logAccess(record.EntityInfo.Name, record.PrimaryKey, 'record');\n }\n }\n\n /** @deprecated Use {@link OnRecordReady}. */\n onRecordReady(record: BaseEntity): void {\n return this.OnRecordReady(record);\n }\n\n OnSaved(record: BaseEntity): void {\n this.RecordSaved.emit(record);\n }\n\n /** @deprecated Use {@link OnSaved}. */\n onSaved(record: BaseEntity): void {\n return this.OnSaved(record);\n }\n\n OnEditModeChanged(editing: boolean): void {\n this.EditModeChanged.emit(editing);\n }\n\n OnNotification(event: FormNotificationEvent): void {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n }\n\n /** @deprecated Use {@link OnNotification}. */\n onNotification(event: FormNotificationEvent): void {\n return this.OnNotification(event);\n }\n\n /** Map the form's navigation requests onto Explorer's NavigationService. */\n HandleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a related record from inside an open form: force a new tab so the\n // parent record stays intact in single-resource mode.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n case 'dismiss':\n this.RecordDismissed.emit();\n break;\n case 'create-related': {\n // A FK field wants a new related record created. Open the related entity's form\n // as a dialog/slide-in (prefilled), then hand the saved record back so the field\n // can select it.\n const ref = this.formPresenter.Open({\n EntityName: event.EntityName,\n Presentation: event.Presentation ?? 'dialog',\n NewRecordValues: event.NewRecordValues,\n Provider: event.Provider,\n });\n ref.AfterSaved().then(created => event.Complete(created));\n break;\n }\n }\n }\n\n /** @deprecated Use {@link HandleNavigation}. */\n handleNavigation(event: FormNavigationEvent): void {\n return this.HandleNavigation(event);\n }\n}\n","@if (DeletedRemotely) {\n <mj-alert\n class=\"single-record-deleted-banner\"\n Variant=\"warning\"\n Title=\"This record was deleted by another user\"\n Message=\"It no longer exists. What you see here is the last copy that was loaded.\">\n <button actions mjButton Variant=\"secondary\" Size=\"sm\" (click)=\"OnCloseDeletedRecord()\">Close</button>\n </mj-alert>\n}\n<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (EditModeChanged)=\"OnEditModeChanged($event)\"\n (Dismissed)=\"recordDismissed.emit()\"\n (FormCreated)=\"onFormCreated($event)\">\n</mj-entity-form-host>\n"]}
1
+ {"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, EventEmitter, inject, Input, OnDestroy, Output, ViewChild } from '@angular/core';\nimport { CompositeKey, BaseEntity } from '@memberjunction/core';\nimport { EntityFormMode, FormNavigationEvent, FormNotificationEvent, MJFormPresenterService, MjEntityFormHostComponent, MjRecordPreviewPopoverService } from '@memberjunction/ng-base-forms';\nimport type { BaseFormComponent, FormCompositionSnapshot } from '@memberjunction/ng-base-forms';\nimport { Subscription } from 'rxjs';\nimport { GetDeletedRecordFromEvent, NavigationService, RecentAccessService, RecordIdentityMatches, SafeDetectChanges, SharedService } from '@memberjunction/ng-shared';\nimport { MJEvent, MJGlobal } from '@memberjunction/global';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { ConversationsRuntime } from '@memberjunction/conversations-runtime';\n\n/**\n * Explorer-side host for a single entity record in the main tab area.\n *\n * This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`\n * (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving\n * the form (class / custom / interactive override + variants), loading the\n * record, dynamically creating the form, binding it, and tearing it down.\n *\n * SingleRecordComponent's only remaining job is the **Explorer mapping**:\n * translating the host's framework-agnostic events into Explorer services —\n * `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},\n * record loads → {@link RecentAccessService} — none of which belong in a Generic\n * component.\n */\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent implements OnDestroy {\n @Input() public PrimaryKey: CompositeKey = new CompositeKey();\n @Input() public entityName: string | null = '';\n @Input() public NewRecordValues: string | Record<string, unknown> | null = '';\n /**\n * `'standard'` asks the host for the CodeGen form even when a custom form is\n * registered (MJ#4755). The mount-time mode; later changes go through\n * {@link SwitchFormMode} so the host's unsaved-work guard applies.\n */\n @Input() public FormMode: EntityFormMode = 'default';\n\n /**\n * Re-emitted host `FormModeChange`: the mode changed inside the host — the\n * strip, a {@link SwitchFormMode} call, or an interactive-variant pick\n * (which returns to `'default'`).\n */\n @Output() public FormModeChange: EventEmitter<EntityFormMode> = new EventEmitter<EntityFormMode>();\n\n /** @deprecated Use {@link NewRecordValues}. */\n @Input() public set newRecordValues(value: string | Record<string, unknown> | null) {\n this.NewRecordValues = value;\n }\n /** @deprecated Use {@link NewRecordValues}. */\n public get newRecordValues(): string | Record<string, unknown> | null {\n return this.NewRecordValues;\n }\n\n @Output() public LoadComplete: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * @deprecated Use {@link LoadComplete}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (loadComplete) keeps working. Must stay AFTER LoadComplete: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public loadComplete = this.LoadComplete;\n @Output() public RecordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n\n /**\n * @deprecated Use {@link RecordSaved}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (recordSaved) keeps working. Must stay AFTER RecordSaved: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public recordSaved = this.RecordSaved;\n /** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */\n @Output() public RecordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * @deprecated Use {@link RecordDismissed}.\n *\n * The same emitter under the old binding name, so a template still binding\n * (recordDismissed) keeps working. Must stay AFTER RecordDismissed: class fields\n * initialise in order, and the other way round this captures undefined.\n */\n @Output() public recordDismissed = this.RecordDismissed;\n\n /**\n * Edit mode of the hosted form started (true) or ended (false). Relayed from\n * `<mj-entity-form-host>` so the resource wrapper can tell the shell to\n * promote this tab. {@link IsEditing} remains as the synchronous fallback.\n */\n @Output() public EditModeChanged: EventEmitter<boolean> = new EventEmitter<boolean>();\n\n /**\n * The live form's composition — sections, related grids, contributions, and the slots\n * the form actually emits. Re-emitted on every chrome resolve.\n *\n * The form is created dynamically inside `<mj-entity-form-host>`, so this relay is the\n * only route the snapshot has out to Explorer.\n */\n @Output() public CompositionChanged: EventEmitter<FormCompositionSnapshot> = new EventEmitter<FormCompositionSnapshot>();\n\n /**\n * Emitted when the user clicks \"Ask AI\" in the record form toolbar.\n * Also requests expansion of the floating chat overlay via ConversationsRuntime.\n */\n @Output() public AskAIRequested: EventEmitter<void> = new EventEmitter<void>();\n\n @ViewChild(MjEntityFormHostComponent) private formHost?: MjEntityFormHostComponent;\n\n /**\n * Switch the mounted form between custom and standard (MJ#4755) through the\n * host's guarded switch. False when refused (unsaved work — the host has\n * already raised a warning) or when no host is mounted yet.\n */\n public SwitchFormMode(mode: EntityFormMode): boolean {\n return this.formHost?.SwitchFormMode(mode) ?? false;\n }\n\n /**\n * True while the hosted form is in edit mode. The host already exposes the\n * live form instance, so this is a read, not a new event pipeline.\n *\n * MJ forms are read-only until the user explicitly clicks Edit, so edit mode\n * is a deliberate gesture and a sound proxy for \"there is work in here worth\n * protecting\" — which is all the preview-tab replacement guard needs.\n */\n public IsEditing(): boolean {\n return this.formHost?.Form?.EditMode === true;\n }\n\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private formPresenter = inject(MJFormPresenterService);\n private recentAccessService = new RecentAccessService();\n private cdr = inject(ChangeDetectorRef);\n\n constructor() {\n super();\n if (!MjRecordPreviewPopoverService.FallbackNavigationHandler) {\n MjRecordPreviewPopoverService.FallbackNavigationHandler = (event: FormNavigationEvent) => {\n if (event.Kind === 'record') {\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n }\n };\n }\n }\n\n /**\n * True once another user (another session) has deleted the record shown here.\n * The tab is NOT closed under the user — they may be reading it — so a banner\n * says so and offers to close it. Deletes in this session are handled by the\n * shell, which closes the tab outright.\n */\n public DeletedRemotely = false;\n\n private remoteDeleteSub: Subscription | null = MJGlobal.Instance.GetEventListener()\n .subscribe((event: MJEvent) => this.onGlobalEvent(event));\n\n /** Close this record's tab (the remote-delete banner's Close button). */\n OnCloseDeletedRecord(): void {\n this.RecordDismissed.emit();\n }\n\n private onGlobalEvent(event: MJEvent): void {\n if (this.DeletedRemotely) {\n return;\n }\n const deleted = GetDeletedRecordFromEvent(event);\n if (!deleted?.Remote || !this.PrimaryKey?.HasValue) {\n return;\n }\n if (RecordIdentityMatches(this.entityName, this.PrimaryKey.ToURLSegment(), deleted)) {\n this.DeletedRemotely = true;\n SafeDetectChanges(this.cdr);\n }\n }\n\n /** Unblock the shell's first-resource-load gate (success or error). */\n OnLoadComplete(): void {\n this.LoadComplete.emit();\n }\n\n /** @deprecated Use {@link OnLoadComplete}. */\n onLoadComplete(): void {\n return this.OnLoadComplete();\n }\n\n private compositionSub: Subscription | null = null;\n\n /**\n * Bind to the newly created form's composition stream.\n *\n * The previous subscription is dropped first: the host re-creates the form when the\n * user switches variants, and a retained subscription would let the replaced form\n * keep publishing over the live one.\n */\n onFormCreated(form: BaseFormComponent): void {\n this.compositionSub?.unsubscribe();\n this.compositionSub = form.CompositionChanged.subscribe((snapshot: FormCompositionSnapshot) =>\n this.CompositionChanged.emit(snapshot),\n );\n if (form.CompositionSnapshot) {\n this.CompositionChanged.emit(form.CompositionSnapshot);\n }\n }\n\n ngOnDestroy(): void {\n this.compositionSub?.unsubscribe();\n this.compositionSub = null;\n this.remoteDeleteSub?.unsubscribe();\n this.remoteDeleteSub = null;\n }\n\n /** Log access for existing records once the form's record is ready. */\n OnRecordReady(record: BaseEntity): void {\n if (record?.IsSaved) {\n this.recentAccessService.logAccess(record.EntityInfo.Name, record.PrimaryKey, 'record');\n }\n }\n\n /** @deprecated Use {@link OnRecordReady}. */\n onRecordReady(record: BaseEntity): void {\n return this.OnRecordReady(record);\n }\n\n OnSaved(record: BaseEntity): void {\n this.RecordSaved.emit(record);\n }\n\n /** @deprecated Use {@link OnSaved}. */\n onSaved(record: BaseEntity): void {\n return this.OnSaved(record);\n }\n\n OnEditModeChanged(editing: boolean): void {\n this.EditModeChanged.emit(editing);\n }\n\n OnAskAIRequested(): void {\n this.AskAIRequested.emit();\n ConversationsRuntime.Instance.Bridge.RequestExpandOverlay();\n }\n\n OnNotification(event: FormNotificationEvent): void {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n }\n\n /** @deprecated Use {@link OnNotification}. */\n onNotification(event: FormNotificationEvent): void {\n return this.OnNotification(event);\n }\n\n /** Map the form's navigation requests onto Explorer's NavigationService. */\n HandleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a related record from inside an open form: force a new tab so the\n // parent record stays intact in single-resource mode.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n case 'dismiss':\n this.RecordDismissed.emit();\n break;\n case 'create-related': {\n // A FK field wants a new related record created. Open the related entity's form\n // as a dialog/slide-in (prefilled), then hand the saved record back so the field\n // can select it.\n const ref = this.formPresenter.Open({\n EntityName: event.EntityName,\n Presentation: event.Presentation ?? 'dialog',\n NewRecordValues: event.NewRecordValues,\n Provider: event.Provider,\n });\n ref.AfterSaved().then(created => event.Complete(created));\n break;\n }\n }\n }\n\n /** @deprecated Use {@link HandleNavigation}. */\n handleNavigation(event: FormNavigationEvent): void {\n return this.HandleNavigation(event);\n }\n}\n","@if (DeletedRemotely) {\n <mj-alert\n class=\"single-record-deleted-banner\"\n Variant=\"warning\"\n Title=\"This record was deleted by another user\"\n Message=\"It no longer exists. What you see here is the last copy that was loaded.\">\n <button actions mjButton Variant=\"secondary\" Size=\"sm\" (click)=\"OnCloseDeletedRecord()\">Close</button>\n </mj-alert>\n}\n<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [FormMode]=\"FormMode\"\n (FormModeChange)=\"FormModeChange.emit($event)\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (EditModeChanged)=\"OnEditModeChanged($event)\"\n (AskAIRequested)=\"OnAskAIRequested()\"\n (Dismissed)=\"recordDismissed.emit()\"\n (FormCreated)=\"onFormCreated($event)\">\n</mj-entity-form-host>\n"]}
@@ -49,6 +49,7 @@ export declare class UserNotificationsComponent extends BaseAngularComponent imp
49
49
  private navigationService;
50
50
  private appManager;
51
51
  private cdr;
52
+ private ngZone;
52
53
  RadioSelected: ReadFilterOption;
53
54
  /** @deprecated Use {@link RadioSelected}. */
54
55
  get radioSelected(): ReadFilterOption;
@@ -89,11 +90,18 @@ export declare class UserNotificationsComponent extends BaseAngularComponent imp
89
90
  ngAfterViewInit(): void;
90
91
  ngOnDestroy(): void;
91
92
  private loadNotificationTypes;
93
+ private notificationsToShowCache;
94
+ private notificationsToShowKey;
95
+ private unreadNotificationsCache;
96
+ private unreadNotificationsKey;
97
+ private readNotificationsCache;
98
+ private readNotificationsKey;
99
+ private computeAllNotificationsSignature;
92
100
  get NotificationsToShow(): MJUserNotificationEntity[];
93
101
  /**
94
102
  * The filtered notifications as render-ready day groups. Memoized on a composite
95
- * key of the inputs so the getter stays cheap across change-detection passes and
96
- * the (time-sensitive) VM snapshots never rebuild mid-pass.
103
+ * key of the inputs BEFORE filtering `AllNotifications`, so the getter stays $O(1)$
104
+ * across change-detection passes and the (time-sensitive) VM snapshots never rebuild mid-pass.
97
105
  */
98
106
  get Groups(): NotificationGroup[];
99
107
  private buildGroups;
@@ -1 +1 @@
1
- {"version":3,"file":"user-notifications.component.d.ts","sourceRoot":"","sources":["../../../src/lib/user-notifications/user-notifications.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"user-notifications.component.d.ts","sourceRoot":"","sources":["../../../src/lib/user-notifications/user-notifications.component.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Component, ChangeDetectorRef, inject } from '@angular/core';
1
+ import { Component, ChangeDetectorRef, NgZone, inject } from '@angular/core';
2
2
  import { SharedService } from '@memberjunction/ng-shared';
3
3
  import { MJUserNotificationEntity, UserInfoEngine } from '@memberjunction/core-entities';
4
4
  import { CompositeKey } from '@memberjunction/core';
@@ -368,6 +368,7 @@ export class UserNotificationsComponent extends BaseAngularComponent {
368
368
  navigationService;
369
369
  appManager;
370
370
  cdr = inject(ChangeDetectorRef);
371
+ ngZone = inject(NgZone, { optional: true });
371
372
  RadioSelected = 'All';
372
373
  /** @deprecated Use {@link RadioSelected}. */
373
374
  get radioSelected() {
@@ -436,13 +437,34 @@ export class UserNotificationsComponent extends BaseAngularComponent {
436
437
  async ngOnInit() {
437
438
  this.loadNotificationTypes();
438
439
  // Refresh the snapshot relative-time labels between change-detection passes.
439
- this.relativeTimer = setInterval(() => {
440
- for (const vm of this.vmCache.values()) {
441
- vm.Relative = this.relativeTime(vm.N.__mj_CreatedAt);
442
- }
443
- this.groupsKey = ''; // day buckets can shift at midnight
444
- this.cdr.markForCheck();
445
- }, RELATIVE_REFRESH_MS);
440
+ // Run outside NgZone so the 60s timer only triggers Angular CD when a label actually changes.
441
+ const startTimer = () => {
442
+ this.relativeTimer = setInterval(() => {
443
+ let anyChanged = false;
444
+ for (const vm of this.vmCache.values()) {
445
+ const next = this.relativeTime(vm.N.__mj_CreatedAt);
446
+ if (vm.Relative !== next) {
447
+ vm.Relative = next;
448
+ anyChanged = true;
449
+ }
450
+ }
451
+ this.groupsKey = ''; // day buckets can shift at midnight
452
+ if (anyChanged) {
453
+ if (this.ngZone) {
454
+ this.ngZone.run(() => this.cdr.markForCheck());
455
+ }
456
+ else {
457
+ this.cdr.markForCheck();
458
+ }
459
+ }
460
+ }, RELATIVE_REFRESH_MS);
461
+ };
462
+ if (this.ngZone) {
463
+ this.ngZone.runOutsideAngular(startTimer);
464
+ }
465
+ else {
466
+ startTimer();
467
+ }
446
468
  }
447
469
  ngAfterViewInit() {
448
470
  this.SharedService.InvokeManualResize(); // make sure the notifications component is sized correctly
@@ -468,17 +490,38 @@ export class UserNotificationsComponent extends BaseAngularComponent {
468
490
  // ========================================================================
469
491
  // Data shaping (filter → view models → day groups)
470
492
  // ========================================================================
493
+ notificationsToShowCache = [];
494
+ notificationsToShowKey = '';
495
+ unreadNotificationsCache = [];
496
+ unreadNotificationsKey = '';
497
+ readNotificationsCache = [];
498
+ readNotificationsKey = '';
499
+ computeAllNotificationsSignature() {
500
+ const all = this.AllNotifications;
501
+ let unreadCount = 0;
502
+ for (let i = 0; i < all.length; i++) {
503
+ if (all[i].Unread)
504
+ unreadCount++;
505
+ }
506
+ const baseKey = `${all.length}|${unreadCount}|${all[0]?.ID ?? ''}|${all[all.length - 1]?.ID ?? ''}`;
507
+ return { all, unreadCount, baseKey };
508
+ }
471
509
  get NotificationsToShow() {
510
+ const { all, baseKey } = this.computeAllNotificationsSignature();
511
+ const key = `${baseKey}|${this.RadioSelected}|${this.SelectedTypeFilter ?? ''}|${this.CurrentFilter}`;
512
+ if (key === this.notificationsToShowKey) {
513
+ return this.notificationsToShowCache;
514
+ }
472
515
  let temp = [];
473
516
  switch (this.RadioSelected) {
474
517
  case 'All':
475
- temp = this.AllNotifications;
518
+ temp = all;
476
519
  break;
477
520
  case 'Unread':
478
- temp = this.AllNotifications.filter(n => n.Unread);
521
+ temp = all.filter(n => n.Unread);
479
522
  break;
480
523
  case 'Read':
481
- temp = this.AllNotifications.filter(n => !n.Unread);
524
+ temp = all.filter(n => !n.Unread);
482
525
  break;
483
526
  }
484
527
  // Apply type filter if selected
@@ -486,24 +529,26 @@ export class UserNotificationsComponent extends BaseAngularComponent {
486
529
  temp = temp.filter(n => UUIDsEqual(n.NotificationTypeID, this.SelectedTypeFilter));
487
530
  }
488
531
  // Apply text filter if it is not empty
489
- if (this.CurrentFilter.trim().length > 0) {
532
+ const filterLower = this.CurrentFilter.trim().toLowerCase();
533
+ if (filterLower.length > 0) {
490
534
  // check for inclusion of filter value in title or message
491
- temp = temp.filter(n => n.Title?.toLowerCase().includes(this.CurrentFilter.trim().toLowerCase()) ||
492
- n.Message?.toLowerCase().includes(this.CurrentFilter.trim().toLowerCase()));
535
+ temp = temp.filter(n => n.Title?.toLowerCase().includes(filterLower) ||
536
+ n.Message?.toLowerCase().includes(filterLower));
493
537
  }
538
+ this.notificationsToShowKey = key;
539
+ this.notificationsToShowCache = temp;
494
540
  return temp;
495
541
  }
496
542
  /**
497
543
  * The filtered notifications as render-ready day groups. Memoized on a composite
498
- * key of the inputs so the getter stays cheap across change-detection passes and
499
- * the (time-sensitive) VM snapshots never rebuild mid-pass.
544
+ * key of the inputs BEFORE filtering `AllNotifications`, so the getter stays $O(1)$
545
+ * across change-detection passes and the (time-sensitive) VM snapshots never rebuild mid-pass.
500
546
  */
501
547
  get Groups() {
502
- const list = this.NotificationsToShow;
503
- const unread = list.filter(n => n.Unread).length;
504
- const key = `${list.length}|${unread}|${this.RadioSelected}|${this.SelectedTypeFilter}|${this.CurrentFilter}|${list[0]?.ID ?? ''}|${list[list.length - 1]?.ID ?? ''}`;
548
+ const { baseKey } = this.computeAllNotificationsSignature();
549
+ const key = `${baseKey}|${this.RadioSelected}|${this.SelectedTypeFilter ?? ''}|${this.CurrentFilter}`;
505
550
  if (key !== this.groupsKey) {
506
- this.groups = this.buildGroups(list);
551
+ this.groups = this.buildGroups(this.NotificationsToShow);
507
552
  this.groupsKey = key;
508
553
  }
509
554
  return this.groups;
@@ -718,10 +763,20 @@ export class UserNotificationsComponent extends BaseAngularComponent {
718
763
  return SharedService.UserNotifications;
719
764
  }
720
765
  get UnreadNotifications() {
721
- return this.AllNotifications.filter(n => n.Unread);
766
+ const { all, baseKey } = this.computeAllNotificationsSignature();
767
+ if (baseKey !== this.unreadNotificationsKey) {
768
+ this.unreadNotificationsKey = baseKey;
769
+ this.unreadNotificationsCache = all.filter(n => n.Unread);
770
+ }
771
+ return this.unreadNotificationsCache;
722
772
  }
723
773
  get ReadNotifications() {
724
- return this.AllNotifications.filter(n => !n.Unread);
774
+ const { all, baseKey } = this.computeAllNotificationsSignature();
775
+ if (baseKey !== this.readNotificationsKey) {
776
+ this.readNotificationsKey = baseKey;
777
+ this.readNotificationsCache = all.filter(n => !n.Unread);
778
+ }
779
+ return this.readNotificationsCache;
725
780
  }
726
781
  SelectReadOption(option) {
727
782
  this.RadioSelected = option;