@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.
- package/LICENSE +7 -0
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +5 -5
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +11 -2
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +79 -23
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +28 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/services/lazy-module-registry.d.ts +48 -8
- package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
- package/dist/lib/services/lazy-module-registry.js +103 -23
- package/dist/lib/services/lazy-module-registry.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +2 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +58 -42
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts +29 -0
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js +72 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts +26 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +140 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts +43 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js +102 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts +21 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js +30 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts +63 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js +143 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +228 -2
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +1073 -113
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +2 -0
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +41 -4
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +400 -240
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +4 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +14 -3
- package/dist/lib/shell/shell.module.js.map +1 -1
- 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":"AAAA,OAAO,EAEL,MAAM,EACN,SAAS,EACT,aAAa,EAEb,UAAU,EACV,cAAc,EACd,mBAAmB,EAKnB,iBAAiB,EAGjB,YAAY,EAEb,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,
|
|
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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"}
|