@memberjunction/ng-explorer-core 5.50.0 → 6.1.0-edge.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.
Files changed (55) hide show
  1. package/LICENSE +7 -0
  2. package/dist/app-routing.module.d.ts.map +1 -1
  3. package/dist/app-routing.module.js +5 -5
  4. package/dist/app-routing.module.js.map +1 -1
  5. package/dist/generated/lazy-feature-config.d.ts +11 -2
  6. package/dist/generated/lazy-feature-config.d.ts.map +1 -1
  7. package/dist/generated/lazy-feature-config.js +79 -23
  8. package/dist/generated/lazy-feature-config.js.map +1 -1
  9. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
  10. package/dist/lib/resource-wrappers/dashboard-resource.component.js +28 -0
  11. package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
  12. package/dist/lib/services/lazy-module-registry.d.ts +48 -8
  13. package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
  14. package/dist/lib/services/lazy-module-registry.js +103 -23
  15. package/dist/lib/services/lazy-module-registry.js.map +1 -1
  16. package/dist/lib/shell/components/header/app-nav.component.d.ts +2 -1
  17. package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
  18. package/dist/lib/shell/components/header/app-nav.component.js +58 -42
  19. package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
  20. package/dist/lib/shell/components/record-open/record-bar.component.d.ts +29 -0
  21. package/dist/lib/shell/components/record-open/record-bar.component.d.ts.map +1 -0
  22. package/dist/lib/shell/components/record-open/record-bar.component.js +72 -0
  23. package/dist/lib/shell/components/record-open/record-bar.component.js.map +1 -0
  24. package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts +26 -0
  25. package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -0
  26. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +140 -0
  27. package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -0
  28. package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts +43 -0
  29. package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts.map +1 -0
  30. package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js +102 -0
  31. package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js.map +1 -0
  32. package/dist/lib/shell/components/record-open/record-switcher.service.d.ts +21 -0
  33. package/dist/lib/shell/components/record-open/record-switcher.service.d.ts.map +1 -0
  34. package/dist/lib/shell/components/record-open/record-switcher.service.js +30 -0
  35. package/dist/lib/shell/components/record-open/record-switcher.service.js.map +1 -0
  36. package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts +63 -0
  37. package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts.map +1 -0
  38. package/dist/lib/shell/components/record-open/records-hub-pill.component.js +143 -0
  39. package/dist/lib/shell/components/record-open/records-hub-pill.component.js.map +1 -0
  40. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +228 -2
  41. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  42. package/dist/lib/shell/components/tabs/tab-container.component.js +1073 -113
  43. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  44. package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
  45. package/dist/lib/shell/services/sharing-center-dialog-host.component.js +2 -0
  46. package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
  47. package/dist/lib/shell/shell.component.d.ts +41 -4
  48. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  49. package/dist/lib/shell/shell.component.js +400 -240
  50. package/dist/lib/shell/shell.component.js.map +1 -1
  51. package/dist/lib/shell/shell.module.d.ts +4 -1
  52. package/dist/lib/shell/shell.module.d.ts.map +1 -1
  53. package/dist/lib/shell/shell.module.js +14 -3
  54. package/dist/lib/shell/shell.module.js.map +1 -1
  55. package/package.json +62 -59
@@ -0,0 +1,63 @@
1
+ import { OnInit, OnDestroy, EventEmitter } from '@angular/core';
2
+ import { WorkspaceTab } from '@memberjunction/ng-base-application';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Persistent "Records" nav pill (records-style record opens).
6
+ *
7
+ * The invariant this design guarantees: no matter where you are in the app,
8
+ * any open record is at most 2 clicks away — the pill (1st click) resumes
9
+ * the LAST-VIEWED record; the strip that appears while viewing records gets
10
+ * you to any other open record (2nd click).
11
+ *
12
+ * Renders only while at least one record is open, with a count badge. Shows
13
+ * the active state when the user is currently viewing a record. Lives at the
14
+ * shell level (after the app nav) so it persists across app switches — the
15
+ * records pool is global, not per-app.
16
+ */
17
+ export declare class RecordsHubPillComponent implements OnInit, OnDestroy {
18
+ private workspaceManager;
19
+ private cdr;
20
+ private breakpoint;
21
+ private recordSwitcher;
22
+ /** Emitted after the pill resumes a record (hosts may close drawers etc.) */
23
+ Activated: EventEmitter<void>;
24
+ /** Open record tabs (any app) */
25
+ RecordTabs: WorkspaceTab[];
26
+ /**
27
+ * ALL open records including workspace-docked ones — the MOBILE pill's
28
+ * universe (docked/region composition is a desktop concept; the mobile
29
+ * switcher sheet lists both, and the badge must match its row count).
30
+ */
31
+ AllRecordTabs: WorkspaceTab[];
32
+ /** True when the workspace's active tab is a record (pill shows active) */
33
+ ViewingRecord: boolean;
34
+ private activeTabId;
35
+ /** Last record tab the user actually viewed — the pill's resume target */
36
+ private lastViewedRecordTabId;
37
+ private sub?;
38
+ private breakpointSub?;
39
+ ngOnInit(): void;
40
+ ngOnDestroy(): void;
41
+ /**
42
+ * Open-record count for the badge. On mobile this includes workspace-docked
43
+ * records — the badge must match the switcher sheet's row count. Desktop
44
+ * badge semantics unchanged (region only; docked records are visible
45
+ * main-layout tabs and don't need a counter).
46
+ */
47
+ get Count(): number;
48
+ /**
49
+ * Pill click.
50
+ *
51
+ * MOBILE: open the record switcher sheet — even while viewing a record
52
+ * (on desktop that case is a no-op because the strip is already showing;
53
+ * on mobile there IS no strip, the sheet is how you switch). The Activated
54
+ * emission closes the drawer the pill lives in.
55
+ *
56
+ * DESKTOP: resume the last-viewed record, falling back to the most
57
+ * recently accessed open record. No-op when already viewing a record.
58
+ */
59
+ Activate(): void;
60
+ static ɵfac: i0.ɵɵFactoryDeclaration<RecordsHubPillComponent, never>;
61
+ static ɵcmp: i0.ɵɵComponentDeclaration<RecordsHubPillComponent, "mj-records-hub-pill", never, {}, { "Activated": "Activated"; }, never, never, true, never>;
62
+ }
63
+ //# sourceMappingURL=records-hub-pill.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records-hub-pill.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/record-open/records-hub-pill.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,SAAS,EAA6B,YAAY,EAAU,MAAM,eAAe,CAAC;AAC9G,OAAO,EAAyB,YAAY,EAAE,MAAM,qCAAqC,CAAC;;AAK1F;;;;;;;;;;;;GAYG;AACH,qBAMa,uBAAwB,YAAW,MAAM,EAAE,SAAS;IAC/D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,cAAc,CAAiC;IAEvD,6EAA6E;IACnE,SAAS,qBAA4B;IAE/C,iCAAiC;IAC1B,UAAU,EAAE,YAAY,EAAE,CAAM;IACvC;;;;OAIG;IACI,aAAa,EAAE,YAAY,EAAE,CAAM;IAC1C,2EAA2E;IACpE,aAAa,UAAS;IAE7B,OAAO,CAAC,WAAW,CAAuB;IAC1C,0EAA0E;IAC1E,OAAO,CAAC,qBAAqB,CAAuB;IACpD,OAAO,CAAC,GAAG,CAAC,CAAe;IAC3B,OAAO,CAAC,aAAa,CAAC,CAAe;IAErC,QAAQ,IAAI,IAAI;IA6BhB,WAAW,IAAI,IAAI;IAKnB;;;;;OAKG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;;;;;;;;;OAUG;IACH,QAAQ,IAAI,IAAI;yCAjFL,uBAAuB;2CAAvB,uBAAuB;CAmGnC"}
@@ -0,0 +1,143 @@
1
+ import { Component, ChangeDetectorRef, Output, EventEmitter, inject } from '@angular/core';
2
+ import { WorkspaceStateManager } from '@memberjunction/ng-base-application';
3
+ import { IsRecordsRegionTab, IsRecordsTabConfiguration, SafeDetectChanges, ExplorerBreakpointService } from '@memberjunction/ng-shared';
4
+ import { RecordSwitcherService } from './record-switcher.service';
5
+ import * as i0 from "@angular/core";
6
+ function RecordsHubPillComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
7
+ const _r1 = i0.ɵɵgetCurrentView();
8
+ i0.ɵɵdomElementStart(0, "button", 1);
9
+ i0.ɵɵdomListener("click", function RecordsHubPillComponent_Conditional_0_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Activate()); });
10
+ i0.ɵɵdomElement(1, "i", 2);
11
+ i0.ɵɵdomElementStart(2, "span", 3);
12
+ i0.ɵɵtext(3, "Records");
13
+ i0.ɵɵdomElementEnd();
14
+ i0.ɵɵdomElementStart(4, "span", 4);
15
+ i0.ɵɵtext(5);
16
+ i0.ɵɵdomElementEnd()();
17
+ } if (rf & 2) {
18
+ const ctx_r1 = i0.ɵɵnextContext();
19
+ i0.ɵɵclassProp("active", ctx_r1.ViewingRecord);
20
+ i0.ɵɵattribute("aria-current", ctx_r1.ViewingRecord ? "true" : null)("aria-label", "Open records (" + ctx_r1.Count + ")");
21
+ i0.ɵɵadvance(5);
22
+ i0.ɵɵtextInterpolate(ctx_r1.Count);
23
+ } }
24
+ /**
25
+ * Persistent "Records" nav pill (records-style record opens).
26
+ *
27
+ * The invariant this design guarantees: no matter where you are in the app,
28
+ * any open record is at most 2 clicks away — the pill (1st click) resumes
29
+ * the LAST-VIEWED record; the strip that appears while viewing records gets
30
+ * you to any other open record (2nd click).
31
+ *
32
+ * Renders only while at least one record is open, with a count badge. Shows
33
+ * the active state when the user is currently viewing a record. Lives at the
34
+ * shell level (after the app nav) so it persists across app switches — the
35
+ * records pool is global, not per-app.
36
+ */
37
+ export class RecordsHubPillComponent {
38
+ workspaceManager = inject(WorkspaceStateManager);
39
+ cdr = inject(ChangeDetectorRef);
40
+ breakpoint = inject(ExplorerBreakpointService);
41
+ recordSwitcher = inject(RecordSwitcherService);
42
+ /** Emitted after the pill resumes a record (hosts may close drawers etc.) */
43
+ Activated = new EventEmitter();
44
+ /** Open record tabs (any app) */
45
+ RecordTabs = [];
46
+ /**
47
+ * ALL open records including workspace-docked ones — the MOBILE pill's
48
+ * universe (docked/region composition is a desktop concept; the mobile
49
+ * switcher sheet lists both, and the badge must match its row count).
50
+ */
51
+ AllRecordTabs = [];
52
+ /** True when the workspace's active tab is a record (pill shows active) */
53
+ ViewingRecord = false;
54
+ activeTabId = null;
55
+ /** Last record tab the user actually viewed — the pill's resume target */
56
+ lastViewedRecordTabId = null;
57
+ sub;
58
+ breakpointSub;
59
+ ngOnInit() {
60
+ this.sub = this.workspaceManager.Configuration.subscribe(config => {
61
+ // REGION records only: a record docked to the workspace ("Move to
62
+ // Workspace") is a visible main-layout tab — it doesn't count toward
63
+ // the pill's badge and is never a resume target.
64
+ this.RecordTabs = (config?.tabs ?? [])
65
+ .filter(t => IsRecordsRegionTab(t.configuration))
66
+ .sort((a, b) => a.sequence - b.sequence);
67
+ this.AllRecordTabs = (config?.tabs ?? [])
68
+ .filter(t => IsRecordsTabConfiguration(t.configuration))
69
+ .sort((a, b) => a.sequence - b.sequence);
70
+ this.activeTabId = config?.activeTabId ?? null;
71
+ const activeIsRecord = this.RecordTabs.some(t => t.id === this.activeTabId);
72
+ this.ViewingRecord = activeIsRecord;
73
+ if (activeIsRecord) {
74
+ this.lastViewedRecordTabId = this.activeTabId;
75
+ }
76
+ else if (this.lastViewedRecordTabId && !this.RecordTabs.some(t => t.id === this.lastViewedRecordTabId)) {
77
+ this.lastViewedRecordTabId = null; // resume target was closed
78
+ }
79
+ // Zoneless: RxJS-driven mutation needs an explicit CD kick
80
+ SafeDetectChanges(this.cdr);
81
+ });
82
+ // Count/visibility read the breakpoint (mobile badge includes docked
83
+ // records) — re-render on crossings
84
+ this.breakpointSub = this.breakpoint.IsMobile$.subscribe(() => {
85
+ SafeDetectChanges(this.cdr);
86
+ });
87
+ }
88
+ ngOnDestroy() {
89
+ this.sub?.unsubscribe();
90
+ this.breakpointSub?.unsubscribe();
91
+ }
92
+ /**
93
+ * Open-record count for the badge. On mobile this includes workspace-docked
94
+ * records — the badge must match the switcher sheet's row count. Desktop
95
+ * badge semantics unchanged (region only; docked records are visible
96
+ * main-layout tabs and don't need a counter).
97
+ */
98
+ get Count() {
99
+ return this.breakpoint.IsMobile ? this.AllRecordTabs.length : this.RecordTabs.length;
100
+ }
101
+ /**
102
+ * Pill click.
103
+ *
104
+ * MOBILE: open the record switcher sheet — even while viewing a record
105
+ * (on desktop that case is a no-op because the strip is already showing;
106
+ * on mobile there IS no strip, the sheet is how you switch). The Activated
107
+ * emission closes the drawer the pill lives in.
108
+ *
109
+ * DESKTOP: resume the last-viewed record, falling back to the most
110
+ * recently accessed open record. No-op when already viewing a record.
111
+ */
112
+ Activate() {
113
+ if (this.breakpoint.IsMobile) {
114
+ if (this.AllRecordTabs.length === 0) {
115
+ return;
116
+ }
117
+ this.recordSwitcher.Open();
118
+ this.Activated.emit();
119
+ return;
120
+ }
121
+ if (this.ViewingRecord || this.RecordTabs.length === 0) {
122
+ return;
123
+ }
124
+ const target = this.RecordTabs.find(t => t.id === this.lastViewedRecordTabId) ??
125
+ [...this.RecordTabs].sort((a, b) => (b.lastAccessedAt || '').localeCompare(a.lastAccessedAt || ''))[0];
126
+ this.workspaceManager.SetActiveTab(target.id);
127
+ this.Activated.emit();
128
+ }
129
+ static ɵfac = function RecordsHubPillComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RecordsHubPillComponent)(); };
130
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordsHubPillComponent, selectors: [["mj-records-hub-pill"]], outputs: { Activated: "Activated" }, decls: 1, vars: 1, consts: [["type", "button", 1, "records-pill", 3, "active"], ["type", "button", 1, "records-pill", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-file-lines"], [1, "records-pill-label"], [1, "records-pill-count"]], template: function RecordsHubPillComponent_Template(rf, ctx) { if (rf & 1) {
131
+ i0.ɵɵconditionalCreate(0, RecordsHubPillComponent_Conditional_0_Template, 6, 5, "button", 0);
132
+ } if (rf & 2) {
133
+ i0.ɵɵconditional(ctx.Count > 0 ? 0 : -1);
134
+ } }, styles: ["\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: flex;\n align-items: center;\n}\n\n\n\n\n[_nghost-%COMP%]::before {\n content: '';\n width: 1px;\n height: 20px;\n background: var(--mj-border-default);\n margin: 0 var(--mj-space-1);\n flex: 0 0 auto;\n}\n\n\n\n[_nghost-%COMP%]:not(:has(.records-pill))::before {\n display: none;\n}\n\n\n\n\n\n\n.mobile-nav-records[_nghost-%COMP%]::before, .mobile-nav-records [_nghost-%COMP%]::before {\n display: none;\n}\n\n.records-pill[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n flex-shrink: 0;\n}\n\n.records-pill[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.records-pill[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n\n.records-pill.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n\n.records-pill.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.records-pill[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-base);\n}\n\n\n\n.records-pill-count[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}"] });
135
+ }
136
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordsHubPillComponent, [{
137
+ type: Component,
138
+ args: [{ standalone: true, selector: 'mj-records-hub-pill', template: "@if (Count > 0) {\n <button class=\"records-pill\" type=\"button\"\n [class.active]=\"ViewingRecord\"\n [attr.aria-current]=\"ViewingRecord ? 'true' : null\"\n [attr.aria-label]=\"'Open records (' + Count + ')'\"\n (click)=\"Activate()\">\n <i class=\"fa-solid fa-file-lines\" aria-hidden=\"true\"></i>\n <span class=\"records-pill-label\">Records</span>\n <span class=\"records-pill-count\">{{ Count }}</span>\n </button>\n}\n", styles: ["/* Persistent Records nav pill.\n The pill mirrors .nav-item in app-nav.component.css EXACTLY (padding, radius,\n type, hover, focus, and the shared brand active-tint recipe) so it reads as\n part of the nav row; keep the two in sync. A pipe divider separates it from\n the app's own nav items. */\n\n:host {\n display: flex;\n align-items: center;\n}\n\n/* The pipe: separates app nav items from the global Records pill.\n Margin is modest \u2014 the nav row's own column gap adds the rest. */\n:host::before {\n content: '';\n width: 1px;\n height: 20px;\n background: var(--mj-border-default);\n margin: 0 var(--mj-space-1);\n flex: 0 0 auto;\n}\n\n/* Hide the pipe entirely when no records are open (pill renders nothing) */\n:host:not(:has(.records-pill))::before {\n display: none;\n}\n\n/* The pipe separates the pill from nav items in the DESKTOP header row.\n In the mobile drawer (.mobile-nav-records wrapper) the pill sits alone in\n its own section \u2014 the pipe has nothing to divide and renders as a stray\n left rail. */\n:host-context(.mobile-nav-records)::before {\n display: none;\n}\n\n.records-pill {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n flex-shrink: 0;\n}\n\n.records-pill:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.records-pill:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n/* Shared active-tint recipe \u2014 keep in sync with .nav-item.active */\n.records-pill.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n\n.records-pill.active i {\n color: var(--mj-brand-primary);\n}\n\n.records-pill i {\n font-size: var(--mj-text-base);\n}\n\n/* Count badge \u2014 nav-item .badge metrics, brand-primary (a count, not an alert) */\n.records-pill-count {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n"] }]
139
+ }], null, { Activated: [{
140
+ type: Output
141
+ }] }); })();
142
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecordsHubPillComponent, { className: "RecordsHubPillComponent", filePath: "src/lib/shell/components/record-open/records-hub-pill.component.ts", lineNumber: 26 }); })();
143
+ //# sourceMappingURL=records-hub-pill.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records-hub-pill.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/record-open/records-hub-pill.component.ts","../../../../../src/lib/shell/components/record-open/records-hub-pill.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit, OnDestroy, ChangeDetectorRef, Output, EventEmitter, inject } from '@angular/core';\nimport { WorkspaceStateManager, WorkspaceTab } from '@memberjunction/ng-base-application';\nimport { IsRecordsRegionTab, IsRecordsTabConfiguration, SafeDetectChanges, ExplorerBreakpointService } from '@memberjunction/ng-shared';\nimport { Subscription } from 'rxjs';\nimport { RecordSwitcherService } from './record-switcher.service';\n\n/**\n * Persistent \"Records\" nav pill (records-style record opens).\n *\n * The invariant this design guarantees: no matter where you are in the app,\n * any open record is at most 2 clicks away — the pill (1st click) resumes\n * the LAST-VIEWED record; the strip that appears while viewing records gets\n * you to any other open record (2nd click).\n *\n * Renders only while at least one record is open, with a count badge. Shows\n * the active state when the user is currently viewing a record. Lives at the\n * shell level (after the app nav) so it persists across app switches — the\n * records pool is global, not per-app.\n */\n@Component({\n standalone: true,\n selector: 'mj-records-hub-pill',\n templateUrl: './records-hub-pill.component.html',\n styleUrls: ['./records-hub-pill.component.css']\n})\nexport class RecordsHubPillComponent implements OnInit, OnDestroy {\n private workspaceManager = inject(WorkspaceStateManager);\n private cdr = inject(ChangeDetectorRef);\n private breakpoint = inject(ExplorerBreakpointService);\n private recordSwitcher = inject(RecordSwitcherService);\n\n /** Emitted after the pill resumes a record (hosts may close drawers etc.) */\n @Output() Activated = new EventEmitter<void>();\n\n /** Open record tabs (any app) */\n public RecordTabs: WorkspaceTab[] = [];\n /**\n * ALL open records including workspace-docked ones — the MOBILE pill's\n * universe (docked/region composition is a desktop concept; the mobile\n * switcher sheet lists both, and the badge must match its row count).\n */\n public AllRecordTabs: WorkspaceTab[] = [];\n /** True when the workspace's active tab is a record (pill shows active) */\n public ViewingRecord = false;\n\n private activeTabId: string | null = null;\n /** Last record tab the user actually viewed — the pill's resume target */\n private lastViewedRecordTabId: string | null = null;\n private sub?: Subscription;\n private breakpointSub?: Subscription;\n\n ngOnInit(): void {\n this.sub = this.workspaceManager.Configuration.subscribe(config => {\n // REGION records only: a record docked to the workspace (\"Move to\n // Workspace\") is a visible main-layout tab — it doesn't count toward\n // the pill's badge and is never a resume target.\n this.RecordTabs = (config?.tabs ?? [])\n .filter(t => IsRecordsRegionTab(t.configuration))\n .sort((a, b) => a.sequence - b.sequence);\n this.AllRecordTabs = (config?.tabs ?? [])\n .filter(t => IsRecordsTabConfiguration(t.configuration))\n .sort((a, b) => a.sequence - b.sequence);\n this.activeTabId = config?.activeTabId ?? null;\n const activeIsRecord = this.RecordTabs.some(t => t.id === this.activeTabId);\n this.ViewingRecord = activeIsRecord;\n if (activeIsRecord) {\n this.lastViewedRecordTabId = this.activeTabId;\n } else if (this.lastViewedRecordTabId && !this.RecordTabs.some(t => t.id === this.lastViewedRecordTabId)) {\n this.lastViewedRecordTabId = null; // resume target was closed\n }\n // Zoneless: RxJS-driven mutation needs an explicit CD kick\n SafeDetectChanges(this.cdr);\n });\n // Count/visibility read the breakpoint (mobile badge includes docked\n // records) — re-render on crossings\n this.breakpointSub = this.breakpoint.IsMobile$.subscribe(() => {\n SafeDetectChanges(this.cdr);\n });\n }\n\n ngOnDestroy(): void {\n this.sub?.unsubscribe();\n this.breakpointSub?.unsubscribe();\n }\n\n /**\n * Open-record count for the badge. On mobile this includes workspace-docked\n * records — the badge must match the switcher sheet's row count. Desktop\n * badge semantics unchanged (region only; docked records are visible\n * main-layout tabs and don't need a counter).\n */\n get Count(): number {\n return this.breakpoint.IsMobile ? this.AllRecordTabs.length : this.RecordTabs.length;\n }\n\n /**\n * Pill click.\n *\n * MOBILE: open the record switcher sheet — even while viewing a record\n * (on desktop that case is a no-op because the strip is already showing;\n * on mobile there IS no strip, the sheet is how you switch). The Activated\n * emission closes the drawer the pill lives in.\n *\n * DESKTOP: resume the last-viewed record, falling back to the most\n * recently accessed open record. No-op when already viewing a record.\n */\n Activate(): void {\n if (this.breakpoint.IsMobile) {\n if (this.AllRecordTabs.length === 0) {\n return;\n }\n this.recordSwitcher.Open();\n this.Activated.emit();\n return;\n }\n if (this.ViewingRecord || this.RecordTabs.length === 0) {\n return;\n }\n const target =\n this.RecordTabs.find(t => t.id === this.lastViewedRecordTabId) ??\n [...this.RecordTabs].sort((a, b) => (b.lastAccessedAt || '').localeCompare(a.lastAccessedAt || ''))[0];\n this.workspaceManager.SetActiveTab(target.id);\n this.Activated.emit();\n }\n}\n","@if (Count > 0) {\n <button class=\"records-pill\" type=\"button\"\n [class.active]=\"ViewingRecord\"\n [attr.aria-current]=\"ViewingRecord ? 'true' : null\"\n [attr.aria-label]=\"'Open records (' + Count + ')'\"\n (click)=\"Activate()\">\n <i class=\"fa-solid fa-file-lines\" aria-hidden=\"true\"></i>\n <span class=\"records-pill-label\">Records</span>\n <span class=\"records-pill-count\">{{ Count }}</span>\n </button>\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { OnInit, OnDestroy, AfterViewInit, ElementRef, ApplicationRef, EnvironmentInjector, ChangeDetectorRef, EventEmitter } from '@angular/core';
2
2
  import { GoldenLayoutManager, WorkspaceStateManager, ApplicationManager } from '@memberjunction/ng-base-application';
3
+ import { RecordSwitcherEntry } from '../record-open/record-switcher-sheet.component';
3
4
  import { CachedComponentInfo } from './component-cache-manager';
4
5
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
5
6
  import * as i0 from "@angular/core";
@@ -22,6 +23,7 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
22
23
  private cdr;
23
24
  glContainer: ElementRef<HTMLDivElement>;
24
25
  directContentContainer: ElementRef<HTMLDivElement>;
26
+ recordsGlContainer?: ElementRef<HTMLDivElement>;
25
27
  /**
26
28
  * Emitted when the first resource component finishes loading.
27
29
  * This allows the shell to keep showing its loading indicator until the first
@@ -35,12 +37,15 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
35
37
  layoutInitError: EventEmitter<void>;
36
38
  private pinService;
37
39
  private navigationService;
40
+ private recordSwitcher;
38
41
  private subscriptions;
39
42
  private layoutInitRetryCount;
40
43
  private readonly MAX_LAYOUT_INIT_RETRIES;
41
44
  private layoutInitialized;
42
45
  private layoutRestorationComplete;
43
46
  private componentRefs;
47
+ /** Pane-level origin crumbs, one per record pane (see RecordOriginCrumbComponent) */
48
+ private originCrumbRefs;
44
49
  private tabsCurrentlyLoading;
45
50
  private cacheManager;
46
51
  useSingleResourceMode: boolean;
@@ -54,8 +59,144 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
54
59
  contextMenuX: number;
55
60
  contextMenuY: number;
56
61
  contextMenuTabId: string | null;
62
+ /** The control that opened the context menu — focus returns here on close */
63
+ private contextMenuAnchor;
64
+ /**
65
+ * A SEPARATE Golden Layout instance hosting only record tabs. Instantiated
66
+ * directly (not DI) — GoldenLayoutManager has no constructor dependencies
67
+ * and the root-provided instance belongs to the MAIN region. Records get
68
+ * native GL tabs (close/drag-to-split/pin) without ever appearing in the
69
+ * main workspace's tab bar.
70
+ */
71
+ private recordsLayoutManager;
72
+ private recordsLayoutInitialized;
73
+ /** Last active tab id reported by the records GL (guards focus feedback loops) */
74
+ private recordsRegionActiveTabId;
75
+ /**
76
+ * True while records-GL tabs are being created/restored in a batch. GL
77
+ * fires an activation event for EVERY tab it creates; letting those write
78
+ * back into the workspace during init steals focus from the tab the user
79
+ * actually asked for (and persists junk layouts mid-build). Suppress both
80
+ * write-backs while set; the batch ends with one explicit focus.
81
+ */
82
+ private recordsCreatingTabs;
83
+ /** Shell mobile breakpoint (768px) — drives the records mobile surface */
84
+ private breakpoint;
85
+ /**
86
+ * True while the records surface renders MOBILE chrome: strip hidden
87
+ * (headerless GL), record bar shown, splits flattened at render, layout
88
+ * persistence suppressed (see the LayoutChanged guard), move actions
89
+ * hidden. Mirrors the breakpoint; flips via rebuildRecordsLayoutForBreakpoint.
90
+ */
91
+ private mobileRecordsActive;
92
+ /**
93
+ * True only synchronously around Destroy() during a breakpoint-crossing
94
+ * rebuild. Destroy fires TabClosed for EVERY live pane; without this guard
95
+ * the records TabClosed handler would CloseTab each one — a crossing would
96
+ * close every open record. cleanupTabComponent still runs (the cache
97
+ * detach is what lets the re-init reattach content).
98
+ */
99
+ private recordsRebuilding;
100
+ /** True when the deployment runs the records-style record-open model */
101
+ get RecordsStyleActive(): boolean;
102
+ /** Records surface is in mobile chrome (record bar instead of tab strip) */
103
+ get IsMobileRecords(): boolean;
104
+ /** Active record's title, entity icon, and app color for the mobile bar */
105
+ RecordBarTitle: string;
106
+ RecordBarIcon: string;
107
+ RecordBarColor: string;
108
+ /**
109
+ * ALL open records — region AND docked — matching the switcher sheet's
110
+ * row count (docked/region composition is a desktop concept; on mobile
111
+ * they're one list).
112
+ */
113
+ OpenRecordCount: number;
114
+ /** Record switcher sheet visibility (mobile) */
115
+ RecordSwitcherVisible: boolean;
116
+ /** Rows for the record switcher sheet (region + docked records, by sequence) */
117
+ SwitcherEntries: RecordSwitcherEntry[];
118
+ /** Open the record switcher sheet (record bar tap / drawer pill) */
119
+ OpenRecordSwitcher(): void;
120
+ /** Sheet row tapped: activate — same contract as the pill (sync drives GL focus) */
121
+ OnSwitcherActivate(tabId: string): void;
122
+ /**
123
+ * Sheet row ✕: close the record through the SAME path as the tab context
124
+ * menu — GL RemoveTab → TabClosed handler (workspace bookkeeping, close
125
+ * guards, cache eviction all apply). The workspace-level fallback covers
126
+ * the edge where GL never held the tab (zero-height init refusal).
127
+ * The sheet stays open; entries recompute on the config emission.
128
+ */
129
+ OnSwitcherClose(tabId: string): void;
130
+ OnSwitcherVisibleChange(visible: boolean): void;
131
+ /**
132
+ * Recompute the mobile record-surface state (bar inputs + count) from a
133
+ * workspace configuration emission. Cheap and unconditional — the values
134
+ * only render while IsMobileRecords, but keeping them current at all
135
+ * widths makes the breakpoint flip instant.
136
+ */
137
+ private updateRecordSurfaceState;
138
+ /** One switcher sheet row: identity + origin subtitle (crumb semantics) */
139
+ private buildSwitcherEntry;
140
+ /** True while the RECORDS region is the visible surface (active tab is a record) */
141
+ ShowRecordsRegion: boolean;
142
+ /**
143
+ * A tab belongs to the records REGION (not the main workspace layout).
144
+ * Records docked to the workspace ("Move to Workspace") fail this check —
145
+ * they are main-layout tabs everywhere this predicate gates.
146
+ */
147
+ private isRecordTab;
148
+ /**
149
+ * Synchronous, exception-safe change-detection flush. Zoneless + OnPush
150
+ * ancestors make RxJS-driven mutations invisible until SOMETHING runs CD;
151
+ * detectChanges throws when a pass is already in flight, and an unguarded
152
+ * throw inside a promise/subscription silently kills the update (the
153
+ * original "clicked the pill, nothing happened for seconds" bug). markForCheck
154
+ * first so even the re-entrant case gets picked up by the in-flight pass.
155
+ */
156
+ private flushRegionCd;
157
+ /**
158
+ * Keep the RECORDS region in lockstep with the workspace configuration:
159
+ * resolve region visibility (active tab is a record), lazily initialize the
160
+ * records layout on first show (restoring the persisted records layout when
161
+ * it matches the tab set), add/remove record tabs, and focus the active one.
162
+ */
163
+ private syncRecordsRegion;
164
+ /** Add/remove/style record tabs in the records GL to match the configuration */
165
+ private syncRecordsTabs;
166
+ /** Focus a records-region tab without feeding back into SetActiveTab loops */
167
+ private focusRecordsTab;
168
+ /**
169
+ * Initialize the records Golden Layout as soon as record tabs exist —
170
+ * EAGERLY, whether or not the region is visible (its container always has
171
+ * a full layout box). Restores the persisted records layout when its
172
+ * component count matches the current record-tab set; otherwise builds
173
+ * tabs fresh. Creation/restore runs with activation write-backs
174
+ * suppressed (see recordsCreatingTabs).
175
+ */
176
+ private ensureRecordsLayoutInitialized;
177
+ /**
178
+ * Breakpoint crossing: tear the records GL down and let the next sync pass
179
+ * rebuild it in the other chrome mode (mobile: headerless + flattened;
180
+ * desktop: full strip + restored splits). The teardown runs under
181
+ * recordsRebuilding — Destroy() fires TabClosed per pane, and without the
182
+ * guard those events would CloseTab every open record (see the guard's
183
+ * field comment). Content survives via the component cache: cleanup
184
+ * detaches each pane's component, and the re-init's first-show reattaches.
185
+ */
186
+ private rebuildRecordsLayoutForBreakpoint;
187
+ /** Create a record tab in the RECORDS layout (mirror of createTab for main) */
188
+ private createRecordsRegionTab;
57
189
  constructor(layoutManager: GoldenLayoutManager, workspaceManager: WorkspaceStateManager, appManager: ApplicationManager, appRef: ApplicationRef, environmentInjector: EnvironmentInjector, cdr: ChangeDetectorRef);
58
190
  ngOnInit(): void;
191
+ /**
192
+ * RECORDS region event wiring: the separate layout's events route through
193
+ * the SAME handlers as the main layout — content loading, close, pin, and
194
+ * context menu are layout-agnostic; only layout persistence targets its
195
+ * own slot. Activation + persistence write-backs are suppressed during
196
+ * batch creation/restore (recordsCreatingTabs) so GL's per-tab-created
197
+ * events can't steal focus or persist half-built layouts.
198
+ */
199
+ private wireRecordsLayoutEvents;
59
200
  ngAfterViewInit(): void;
60
201
  /**
61
202
  * Initialize Golden Layout and load tabs
@@ -87,6 +228,14 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
87
228
  * are recreated fresh with the new org context.
88
229
  */
89
230
  ReloadAllTabs(): Promise<void>;
231
+ /** Mark all records-region tabs not-loaded and reload the active one (tenant switch) */
232
+ private reloadRecordsRegionTabs;
233
+ /**
234
+ * If the workspace has no tabs left (closing the last record empties it —
235
+ * records skip the manager's keep-last-tab-alive rule), open the active
236
+ * app's default tab so the user lands on a meaningful surface.
237
+ */
238
+ private backfillEmptyWorkspace;
90
239
  ngOnDestroy(): void;
91
240
  /**
92
241
  * Handle window resize events as a fallback safety mechanism.
@@ -188,6 +337,22 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
188
337
  * Create a tab in Golden Layout from workspace tab data
189
338
  */
190
339
  private createTab;
340
+ /**
341
+ * Synchronous best-effort TYPE icon for a tab (the app-colored icon in
342
+ * the tab's type slot — see TabComponentState.typeIcon). Record tabs
343
+ * resolve fully here (entity metadata is loaded); nav tabs start with the
344
+ * app's icon and get upgraded to the nav item's own icon asynchronously
345
+ * (upgradeNavTabIcon).
346
+ */
347
+ private resolveTabTypeIcon;
348
+ /**
349
+ * Resolve and apply a NAV tab's type icon — the async owner of nav-tab
350
+ * icons (GetNavItems is a cached JSON parse: near-instant, but async by
351
+ * contract). Ladder: nav item's own icon → app icon → generic. No-op for
352
+ * record tabs (their icons resolve synchronously). Idempotent: the slot
353
+ * only touches the DOM when the class actually changes.
354
+ */
355
+ private upgradeNavTabIcon;
191
356
  /**
192
357
  * Handle tab shown event for lazy loading
193
358
  */
@@ -232,10 +397,36 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
232
397
  * Used to validate that saved layout matches the tabs array before restoring.
233
398
  */
234
399
  private countLayoutComponents;
400
+ /**
401
+ * A saved records layout is restorable only when its component tabIds are
402
+ * EXACTLY the current record-tab set. Count equality is not enough: a
403
+ * persistence-suppressed mobile session can open one record and close
404
+ * another, leaving a same-sized layout that references a closed tab —
405
+ * restoring it renders a ghost pane (content load fails) until the next
406
+ * sync sweeps it.
407
+ */
408
+ private layoutCoversExactTabSet;
235
409
  /**
236
410
  * Cleanup a tab's component
237
411
  * Detaches from DOM but keeps in cache for potential reuse
238
412
  */
413
+ /**
414
+ * Ensure a record pane's FIRST element is its origin crumb (Matt's
415
+ * pane-level placement — correct in splits, docked panes, and
416
+ * single-resource, unlike the old region-level bar). Recreated on every
417
+ * content attach: the pane container is cleared/re-homed on loads,
418
+ * cache reattaches, and promote/demote moves.
419
+ */
420
+ private ensureRecordOriginCrumb;
421
+ /**
422
+ * Refresh a pane crumb's origin after a config change (re-open
423
+ * re-capture). Also handles the CREATE case: a record first opened
424
+ * WITHOUT an origin (deep link, history recreate) whose re-open just
425
+ * captured one — the pane is already attached, so ensureRecordOriginCrumb
426
+ * never runs again; build the crumb into the live pane here.
427
+ */
428
+ private updateOriginCrumb;
429
+ private destroyOriginCrumb;
239
430
  private cleanupTabComponent;
240
431
  /**
241
432
  * Sync tabs with configuration changes
@@ -244,15 +435,50 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
244
435
  /**
245
436
  * Show context menu
246
437
  */
247
- showContextMenu(x: number, y: number, tabId: string): void;
438
+ showContextMenu(x: number, y: number, tabId: string, anchorEl?: HTMLElement): void;
248
439
  /**
249
440
  * Hide context menu
250
441
  */
251
- hideContextMenu(): void;
442
+ hideContextMenu(restoreFocus?: boolean): void;
443
+ /**
444
+ * role="menu" keyboard model: ArrowUp/Down rove (wrapping) over enabled
445
+ * items, Home/End jump, Escape closes with focus return, Tab dismisses
446
+ * (native menus don't trap Tab).
447
+ */
448
+ onMenuKeydown(event: KeyboardEvent): void;
252
449
  /**
253
450
  * Check if context menu tab is pinned
254
451
  */
255
452
  get isContextTabPinned(): boolean;
453
+ /** Context tab is a records-REGION record — eligible for "Move to Workspace" */
454
+ get canContextMoveToWorkspace(): boolean;
455
+ /** Context tab is a DOCKED record — eligible for "Move to Records" */
456
+ get canContextMoveToRecords(): boolean;
457
+ /**
458
+ * Promote: records region → main workspace layout ("Move to Workspace").
459
+ * ORDER MATTERS: activate FIRST, then flip the flag. With the tab already
460
+ * active, (a) the moved tab stays the user's focus through the membership
461
+ * flip (syncTabsWithConfiguration's tail focuses config.activeTabId), and
462
+ * (b) a single-resource→multi-tab transition triggered by the flip exempts
463
+ * it from the force-pin sweep (it IS the activeTabId).
464
+ */
465
+ onContextMoveToWorkspace(): void;
466
+ /**
467
+ * Demote: main workspace layout → records region ("Move to Records").
468
+ * Same activate-first ordering: the flip's emission then computes
469
+ * showing=true in syncRecordsRegion, so the region surfaces focused on
470
+ * the returned tab. `false` (not undefined) so the choice is explicit in
471
+ * the persisted configuration.
472
+ */
473
+ onContextMoveToRecords(): void;
474
+ /**
475
+ * A move's SOURCE layout auto-activates its next tab when the moved tab is
476
+ * removed, and that GL activation WRITES BACK into the workspace — stomping
477
+ * the moved tab's activation (demote left the user staring at the main
478
+ * layout while the record surfaced in the records region). Re-assert after
479
+ * the removal cascade settles — the assertRecordActivation pattern.
480
+ */
481
+ private assertMovedTabActivation;
256
482
  /**
257
483
  * Toggle pin from context menu
258
484
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tab-container.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/tab-container.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tab-container.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/tab-container.component.ts"],"names":[],"mappings":"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"}