@memberjunction/ng-explorer-core 6.1.3 → 6.2.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/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +23 -3
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +1 -8
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts +7 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +63 -9
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +14 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +72 -12
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/readiness-beacon.d.ts +22 -0
- package/dist/lib/shell/readiness-beacon.d.ts.map +1 -0
- package/dist/lib/shell/readiness-beacon.js +36 -0
- package/dist/lib/shell/readiness-beacon.js.map +1 -0
- package/dist/lib/shell/shell.component.d.ts +13 -1
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +20 -2
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/package.json +52 -52
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Component, Input, inject } from '@angular/core';
|
|
1
|
+
import { Component, Input, inject, ChangeDetectorRef } from '@angular/core';
|
|
2
2
|
import { NavigationService, RecordSourceHasReturnTarget } from '@memberjunction/ng-shared';
|
|
3
|
+
import { CompositeKey, Metadata } from '@memberjunction/core';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
5
|
function RecordOriginCrumbComponent_Conditional_0_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
5
6
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
@@ -10,7 +11,7 @@ function RecordOriginCrumbComponent_Conditional_0_Conditional_0_Conditional_1_Te
|
|
|
10
11
|
} if (rf & 2) {
|
|
11
12
|
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
12
13
|
i0.ɵɵadvance();
|
|
13
|
-
i0.ɵɵtextInterpolate1(" ", ctx_r1.
|
|
14
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.DisplayLabel, " ");
|
|
14
15
|
} }
|
|
15
16
|
function RecordOriginCrumbComponent_Conditional_0_Conditional_0_Conditional_2_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
16
17
|
const _r4 = i0.ɵɵgetCurrentView();
|
|
@@ -47,7 +48,7 @@ function RecordOriginCrumbComponent_Conditional_0_Conditional_0_Template(rf, ctx
|
|
|
47
48
|
} if (rf & 2) {
|
|
48
49
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
49
50
|
i0.ɵɵadvance();
|
|
50
|
-
i0.ɵɵconditional(ctx_r1.
|
|
51
|
+
i0.ɵɵconditional(ctx_r1.DisplayLabel ? 1 : 2);
|
|
51
52
|
} }
|
|
52
53
|
function RecordOriginCrumbComponent_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
53
54
|
i0.ɵɵdomElementStart(0, "span", 0);
|
|
@@ -56,7 +57,7 @@ function RecordOriginCrumbComponent_Conditional_0_Conditional_1_Template(rf, ctx
|
|
|
56
57
|
} if (rf & 2) {
|
|
57
58
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
58
59
|
i0.ɵɵadvance();
|
|
59
|
-
i0.ɵɵtextInterpolate1("From ", ctx_r1.Origin.sourceLabel);
|
|
60
|
+
i0.ɵɵtextInterpolate1("From ", ctx_r1.DisplayLabel || ctx_r1.Origin.sourceLabel);
|
|
60
61
|
} }
|
|
61
62
|
function RecordOriginCrumbComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
62
63
|
i0.ɵɵconditionalCreate(0, RecordOriginCrumbComponent_Conditional_0_Conditional_0_Template, 3, 1)(1, RecordOriginCrumbComponent_Conditional_0_Conditional_1_Template, 2, 1, "span", 0);
|
|
@@ -77,11 +78,64 @@ function RecordOriginCrumbComponent_Conditional_0_Template(rf, ctx) { if (rf & 1
|
|
|
77
78
|
* (fresh loads, cache reattaches after promote/demote, single-resource).
|
|
78
79
|
*/
|
|
79
80
|
export class RecordOriginCrumbComponent {
|
|
80
|
-
|
|
81
|
+
_origin = null;
|
|
82
|
+
asyncResolvedLabel;
|
|
83
|
+
set Origin(value) {
|
|
84
|
+
this._origin = value;
|
|
85
|
+
this.asyncResolvedLabel = undefined;
|
|
86
|
+
this.resolveAsyncLabel();
|
|
87
|
+
}
|
|
88
|
+
get Origin() {
|
|
89
|
+
return this._origin;
|
|
90
|
+
}
|
|
81
91
|
navigationService = inject(NavigationService);
|
|
92
|
+
cdr = inject(ChangeDetectorRef);
|
|
82
93
|
get Clickable() {
|
|
83
94
|
return RecordSourceHasReturnTarget(this.Origin);
|
|
84
95
|
}
|
|
96
|
+
get DisplayLabel() {
|
|
97
|
+
if (this.Origin?.sourceRecordEntity && this.Origin?.sourceRecordId) {
|
|
98
|
+
if (this.asyncResolvedLabel) {
|
|
99
|
+
return this.asyncResolvedLabel;
|
|
100
|
+
}
|
|
101
|
+
const md = Metadata.Provider; // global-provider-ok: shell breadcrumb component displays navigation history using global metadata cache
|
|
102
|
+
const entity = md?.EntityByName(this.Origin.sourceRecordEntity);
|
|
103
|
+
const key = CompositeKey.FromURLSegment(entity, this.Origin.sourceRecordId);
|
|
104
|
+
const cached = md ? md.GetCachedRecordNameOnlyIfCached(this.Origin.sourceRecordEntity, key) : undefined;
|
|
105
|
+
if (cached) {
|
|
106
|
+
return cached;
|
|
107
|
+
}
|
|
108
|
+
const fallbackEntityLabel = entity?.DisplayName || entity?.Name || this.Origin.sourceRecordEntity;
|
|
109
|
+
if (this.Origin.sourceLabel && !this.Origin.sourceLabel.includes(this.Origin.sourceRecordId)) {
|
|
110
|
+
return this.Origin.sourceLabel;
|
|
111
|
+
}
|
|
112
|
+
return fallbackEntityLabel;
|
|
113
|
+
}
|
|
114
|
+
return this.Origin?.sourceLabel;
|
|
115
|
+
}
|
|
116
|
+
resolveAsyncLabel() {
|
|
117
|
+
const md = Metadata.Provider; // global-provider-ok: shell breadcrumb component asynchronously resolves record name using global metadata provider
|
|
118
|
+
if (this.Origin?.sourceRecordEntity && this.Origin?.sourceRecordId && md) {
|
|
119
|
+
const entity = md.EntityByName(this.Origin.sourceRecordEntity);
|
|
120
|
+
const key = CompositeKey.FromURLSegment(entity, this.Origin.sourceRecordId);
|
|
121
|
+
const cached = md.GetCachedRecordNameOnlyIfCached(this.Origin.sourceRecordEntity, key);
|
|
122
|
+
if (cached) {
|
|
123
|
+
this.asyncResolvedLabel = cached;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const entityName = this.Origin.sourceRecordEntity;
|
|
127
|
+
if (typeof md.GetEntityRecordName === 'function') {
|
|
128
|
+
md.GetEntityRecordName(entityName, key).then(name => {
|
|
129
|
+
if (name && this.Origin?.sourceRecordEntity === entityName) {
|
|
130
|
+
this.asyncResolvedLabel = name;
|
|
131
|
+
this.cdr.markForCheck();
|
|
132
|
+
}
|
|
133
|
+
}).catch(() => {
|
|
134
|
+
// Silently ignore async name fetch error, fallback will remain active
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
85
139
|
/** Page segment: full restore — the captured page, section state included */
|
|
86
140
|
async OnPageClick() {
|
|
87
141
|
if (this.Origin) {
|
|
@@ -112,9 +166,9 @@ export class RecordOriginCrumbComponent {
|
|
|
112
166
|
@if (Origin) {
|
|
113
167
|
@if (Clickable) {
|
|
114
168
|
<i class="fa-solid fa-arrow-left crumb-lead" aria-hidden="true"></i>
|
|
115
|
-
@if (
|
|
169
|
+
@if (DisplayLabel) {
|
|
116
170
|
<button type="button" class="crumb-seg" (click)="OnPageClick()" title="Back to where you opened this record">
|
|
117
|
-
{{
|
|
171
|
+
{{ DisplayLabel }}
|
|
118
172
|
</button>
|
|
119
173
|
} @else {
|
|
120
174
|
<button type="button" class="crumb-seg" (click)="OnAppClick()" [title]="'Go to ' + Origin.sourceAppName">
|
|
@@ -129,12 +183,12 @@ export class RecordOriginCrumbComponent {
|
|
|
129
183
|
}
|
|
130
184
|
} @else {
|
|
131
185
|
<!-- Provenance only — no return target (e.g. opened by an agent) -->
|
|
132
|
-
<span class="origin-static">From {{ Origin.sourceLabel }}</span>
|
|
186
|
+
<span class="origin-static">From {{ DisplayLabel || Origin.sourceLabel }}</span>
|
|
133
187
|
}
|
|
134
188
|
}
|
|
135
189
|
`, styles: ["\n :host {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n height: 36px;\n /* Left indent deliberately DEEPER than the first tab's edge (~16px)\n so the crumb reads as a level BENEATH the tab \u2014 hierarchy, not a\n sibling bar. */\n padding: 0 12px 0 24px;\n /* Match the ACTIVE tab's surface so the tab flows into its pane\n (the sunken tone read as a disconnected band between them). */\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n }\n :host:not(:has(*)) { display: none; }\n .crumb-lead {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-right: 6px;\n }\n /* Each breadcrumb level is its OWN link with its own hover */\n .crumb-seg {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 5px 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n font-size: 12px;\n line-height: 1;\n font-family: inherit;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .crumb-seg:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .crumb-seg:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n }\n .crumb-page { font-weight: 500; }\n .crumb-sep { color: var(--mj-text-muted); font-size: 12px; }\n .origin-static {\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n "] }]
|
|
136
190
|
}], null, { Origin: [{
|
|
137
191
|
type: Input
|
|
138
192
|
}] }); })();
|
|
139
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecordOriginCrumbComponent, { className: "RecordOriginCrumbComponent", filePath: "src/lib/shell/components/record-open/record-origin-crumb.component.ts", lineNumber:
|
|
193
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecordOriginCrumbComponent, { className: "RecordOriginCrumbComponent", filePath: "src/lib/shell/components/record-open/record-origin-crumb.component.ts", lineNumber: 111 }); })();
|
|
140
194
|
//# sourceMappingURL=record-origin-crumb.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-origin-crumb.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/record-open/record-origin-crumb.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAuB,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;;;;IA8BtG,oCAA6G;IAArE,8NAAS,oBAAa,KAAC;IAC7D,YACF;IAAA,oBAAS;;;IADP,cACF;IADE,0DACF;;;;IAME,kCAA2C;IAAA,sBAAC;IAAA,oBAAO;IACnD,oCAAwH;IAArE,4OAAS,oBAAa,KAAC;IACxE,YACF;IAAA,oBAAS;;;IADP,eACF;IADE,6DACF;;;;IAPF,oCAAyG;IAAjE,8NAAS,mBAAY,KAAC;IAC5D,YACF;IAAA,oBAAS;IACT,4HAA6B;;;IAHkC,iEAAyC;IACtG,cACF;IADE,4DACF;IACA,cAKC;IALD,uDAKC;;;IAdH,0BAAoE;IAKlE,AAJF,2HAA0B,wFAIjB;;;IAJT,cAcC;IAdD,mDAcC;;;IAGD,kCAA4B;IAAA,YAA6B;IAAA,oBAAO;;;IAApC,cAA6B;IAA7B,yDAA6B;;;IAFzD,AAjBF,gGAAiB,qFAiBR;;;IAjBT,0CAoBC;;AA7CP;;;;;;;;;;;GAWG;AA+FH,MAAM,OAAO,0BAA0B;IAC5B,MAAM,GAA+B,IAAI,CAAC;IAE3C,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEtD,IAAI,SAAS;QACX,OAAO,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,WAAW;QACf,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;oHArBU,0BAA0B;6DAA1B,0BAA0B;YAA1B,6GAAA,wBAAwB,IAAE,wFAA1B,wBAAwB,IAAE;;YAlFnC,kFAAc;;YAAd,qCAsBC;;;iFA4DQ,0BAA0B;cA9FtC,SAAS;2BACE,wBAAwB,cACtB,IAAI,QAKV;oBACJ,aAAa,EAAE,0BAA0B;oBACzC,SAAS,EAAE,0BAA0B;iBACtC,YACS;;;;;;;;;;;;;;;;;;;;;;;;GAwBT;;kBA4DA,KAAK;;kFADK,0BAA0B","sourcesContent":["import { Component, Input, inject } from '@angular/core';\nimport { NavigationService, RecordSourceContext, RecordSourceHasReturnTarget } from '@memberjunction/ng-shared';\n\n/**\n * Pane-level origin crumb — the FIRST element inside a record's golden-layout\n * pane (Matt's placement call, 2026-07-30: no record-chrome refactor; the\n * crumb rides the pane). One instance per record pane, which makes it correct\n * in every layout context the region-level bar could not be:\n * - split views: each pane carries its OWN origin\n * - docked records (main layout, bar or single-resource): provenance intact\n * - edit mode / custom form headers: unaffected — the crumb sits above the\n * form entirely, in shell-owned territory\n * Rendered/positioned by the tab-container wherever record content attaches\n * (fresh loads, cache reattaches after promote/demote, single-resource).\n */\n@Component({\n selector: 'mj-record-origin-crumb',\n standalone: true,\n // The crumb sits INSIDE a GL pane: without these stoppers, clicks bubble\n // to Golden Layout's pane-focus handlers, which re-activate the pane's\n // own tab and STOMP the navigation the click just started (the app\n // segment created the destination tab and then lost the activation race).\n host: {\n '(mousedown)': '$event.stopPropagation()',\n '(click)': '$event.stopPropagation()'\n },\n template: `\n @if (Origin) {\n @if (Clickable) {\n <i class=\"fa-solid fa-arrow-left crumb-lead\" aria-hidden=\"true\"></i>\n @if (Origin.sourceLabel) {\n <button type=\"button\" class=\"crumb-seg\" (click)=\"OnPageClick()\" title=\"Back to where you opened this record\">\n {{ Origin.sourceLabel }}\n </button>\n } @else {\n <button type=\"button\" class=\"crumb-seg\" (click)=\"OnAppClick()\" [title]=\"'Go to ' + Origin.sourceAppName\">\n {{ Origin.sourceAppName }}\n </button>\n @if (Origin.sourceNavLabel) {\n <span class=\"crumb-sep\" aria-hidden=\"true\">›</span>\n <button type=\"button\" class=\"crumb-seg crumb-page\" (click)=\"OnPageClick()\" title=\"Back to where you opened this record\">\n {{ Origin.sourceNavLabel }}\n </button>\n }\n }\n } @else {\n <!-- Provenance only — no return target (e.g. opened by an agent) -->\n <span class=\"origin-static\">From {{ Origin.sourceLabel }}</span>\n }\n }\n `,\n styles: [`\n :host {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n height: 36px;\n /* Left indent deliberately DEEPER than the first tab's edge (~16px)\n so the crumb reads as a level BENEATH the tab — hierarchy, not a\n sibling bar. */\n padding: 0 12px 0 24px;\n /* Match the ACTIVE tab's surface so the tab flows into its pane\n (the sunken tone read as a disconnected band between them). */\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n }\n :host:not(:has(*)) { display: none; }\n .crumb-lead {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-right: 6px;\n }\n /* Each breadcrumb level is its OWN link with its own hover */\n .crumb-seg {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 5px 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n font-size: 12px;\n line-height: 1;\n font-family: inherit;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .crumb-seg:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .crumb-seg:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n }\n .crumb-page { font-weight: 500; }\n .crumb-sep { color: var(--mj-text-muted); font-size: 12px; }\n .origin-static {\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n `]\n})\nexport class RecordOriginCrumbComponent {\n @Input() Origin: RecordSourceContext | null = null;\n\n private navigationService = inject(NavigationService);\n\n get Clickable(): boolean {\n return RecordSourceHasReturnTarget(this.Origin);\n }\n\n /** Page segment: full restore — the captured page, section state included */\n async OnPageClick(): Promise<void> {\n if (this.Origin) {\n await this.navigationService.ReturnToRecordSource(this.Origin);\n }\n }\n\n /** App segment: the app's LANDING page (breadcrumb level semantics) */\n async OnAppClick(): Promise<void> {\n if (this.Origin?.sourceAppId) {\n await this.navigationService.SwitchToAppHome(this.Origin.sourceAppId);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"record-origin-crumb.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/record-open/record-origin-crumb.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, inject, ChangeDetectorRef } from '@angular/core';\nimport { NavigationService, RecordSourceContext, RecordSourceHasReturnTarget } from '@memberjunction/ng-shared';\nimport { CompositeKey, Metadata } from '@memberjunction/core';\n\n/**\n * Pane-level origin crumb — the FIRST element inside a record's golden-layout\n * pane (Matt's placement call, 2026-07-30: no record-chrome refactor; the\n * crumb rides the pane). One instance per record pane, which makes it correct\n * in every layout context the region-level bar could not be:\n * - split views: each pane carries its OWN origin\n * - docked records (main layout, bar or single-resource): provenance intact\n * - edit mode / custom form headers: unaffected — the crumb sits above the\n * form entirely, in shell-owned territory\n * Rendered/positioned by the tab-container wherever record content attaches\n * (fresh loads, cache reattaches after promote/demote, single-resource).\n */\n@Component({\n selector: 'mj-record-origin-crumb',\n standalone: true,\n // The crumb sits INSIDE a GL pane: without these stoppers, clicks bubble\n // to Golden Layout's pane-focus handlers, which re-activate the pane's\n // own tab and STOMP the navigation the click just started (the app\n // segment created the destination tab and then lost the activation race).\n host: {\n '(mousedown)': '$event.stopPropagation()',\n '(click)': '$event.stopPropagation()'\n },\n template: `\n @if (Origin) {\n @if (Clickable) {\n <i class=\"fa-solid fa-arrow-left crumb-lead\" aria-hidden=\"true\"></i>\n @if (DisplayLabel) {\n <button type=\"button\" class=\"crumb-seg\" (click)=\"OnPageClick()\" title=\"Back to where you opened this record\">\n {{ DisplayLabel }}\n </button>\n } @else {\n <button type=\"button\" class=\"crumb-seg\" (click)=\"OnAppClick()\" [title]=\"'Go to ' + Origin.sourceAppName\">\n {{ Origin.sourceAppName }}\n </button>\n @if (Origin.sourceNavLabel) {\n <span class=\"crumb-sep\" aria-hidden=\"true\">›</span>\n <button type=\"button\" class=\"crumb-seg crumb-page\" (click)=\"OnPageClick()\" title=\"Back to where you opened this record\">\n {{ Origin.sourceNavLabel }}\n </button>\n }\n }\n } @else {\n <!-- Provenance only — no return target (e.g. opened by an agent) -->\n <span class=\"origin-static\">From {{ DisplayLabel || Origin.sourceLabel }}</span>\n }\n }\n `,\n styles: [`\n :host {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n height: 36px;\n /* Left indent deliberately DEEPER than the first tab's edge (~16px)\n so the crumb reads as a level BENEATH the tab — hierarchy, not a\n sibling bar. */\n padding: 0 12px 0 24px;\n /* Match the ACTIVE tab's surface so the tab flows into its pane\n (the sunken tone read as a disconnected band between them). */\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n }\n :host:not(:has(*)) { display: none; }\n .crumb-lead {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-right: 6px;\n }\n /* Each breadcrumb level is its OWN link with its own hover */\n .crumb-seg {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 5px 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n font-size: 12px;\n line-height: 1;\n font-family: inherit;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .crumb-seg:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .crumb-seg:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n }\n .crumb-page { font-weight: 500; }\n .crumb-sep { color: var(--mj-text-muted); font-size: 12px; }\n .origin-static {\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n `]\n})\nexport class RecordOriginCrumbComponent {\n private _origin: RecordSourceContext | null = null;\n public asyncResolvedLabel?: string;\n\n @Input()\n set Origin(value: RecordSourceContext | null) {\n this._origin = value;\n this.asyncResolvedLabel = undefined;\n this.resolveAsyncLabel();\n }\n get Origin(): RecordSourceContext | null {\n return this._origin;\n }\n\n private navigationService = inject(NavigationService);\n private cdr = inject(ChangeDetectorRef);\n\n get Clickable(): boolean {\n return RecordSourceHasReturnTarget(this.Origin);\n }\n\n get DisplayLabel(): string | undefined {\n if (this.Origin?.sourceRecordEntity && this.Origin?.sourceRecordId) {\n if (this.asyncResolvedLabel) {\n return this.asyncResolvedLabel;\n }\n const md = Metadata.Provider; // global-provider-ok: shell breadcrumb component displays navigation history using global metadata cache\n const entity = md?.EntityByName(this.Origin.sourceRecordEntity);\n const key = CompositeKey.FromURLSegment(entity, this.Origin.sourceRecordId);\n const cached = md ? md.GetCachedRecordNameOnlyIfCached(this.Origin.sourceRecordEntity, key) : undefined;\n if (cached) {\n return cached;\n }\n const fallbackEntityLabel = entity?.DisplayName || entity?.Name || this.Origin.sourceRecordEntity;\n if (this.Origin.sourceLabel && !this.Origin.sourceLabel.includes(this.Origin.sourceRecordId)) {\n return this.Origin.sourceLabel;\n }\n return fallbackEntityLabel;\n }\n return this.Origin?.sourceLabel;\n }\n\n private resolveAsyncLabel(): void {\n const md = Metadata.Provider; // global-provider-ok: shell breadcrumb component asynchronously resolves record name using global metadata provider\n if (this.Origin?.sourceRecordEntity && this.Origin?.sourceRecordId && md) {\n const entity = md.EntityByName(this.Origin.sourceRecordEntity);\n const key = CompositeKey.FromURLSegment(entity, this.Origin.sourceRecordId);\n const cached = md.GetCachedRecordNameOnlyIfCached(this.Origin.sourceRecordEntity, key);\n if (cached) {\n this.asyncResolvedLabel = cached;\n return;\n }\n const entityName = this.Origin.sourceRecordEntity;\n if (typeof md.GetEntityRecordName === 'function') {\n md.GetEntityRecordName(entityName, key).then(name => {\n if (name && this.Origin?.sourceRecordEntity === entityName) {\n this.asyncResolvedLabel = name;\n this.cdr.markForCheck();\n }\n }).catch(() => {\n // Silently ignore async name fetch error, fallback will remain active\n });\n }\n }\n }\n\n /** Page segment: full restore — the captured page, section state included */\n async OnPageClick(): Promise<void> {\n if (this.Origin) {\n await this.navigationService.ReturnToRecordSource(this.Origin);\n }\n }\n\n /** App segment: the app's LANDING page (breadcrumb level semantics) */\n async OnAppClick(): Promise<void> {\n if (this.Origin?.sourceAppId) {\n await this.navigationService.SwitchToAppHome(this.Origin.sourceAppId);\n }\n }\n}\n"]}
|
|
@@ -47,6 +47,7 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
|
|
|
47
47
|
/** Pane-level origin crumbs, one per record pane (see RecordOriginCrumbComponent) */
|
|
48
48
|
private originCrumbRefs;
|
|
49
49
|
private tabsCurrentlyLoading;
|
|
50
|
+
private pendingRecordShows;
|
|
50
51
|
private cacheManager;
|
|
51
52
|
useSingleResourceMode: boolean;
|
|
52
53
|
private singleResourceComponentRef;
|
|
@@ -188,6 +189,19 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
|
|
|
188
189
|
* `ensureRecordOriginCrumb` once the incoming record attaches.
|
|
189
190
|
*/
|
|
190
191
|
private reloadRecordsTabIfResourceChanged;
|
|
192
|
+
/**
|
|
193
|
+
* Load a record tab's content into its GL container and mark it loaded —
|
|
194
|
+
* the second half of the TabShown handler, split out so a show parked
|
|
195
|
+
* while the region was hidden can be replayed on reveal.
|
|
196
|
+
*/
|
|
197
|
+
private loadShownRecordTab;
|
|
198
|
+
/**
|
|
199
|
+
* Replay the shows parked while the records region was hidden (see the
|
|
200
|
+
* TabShown handler). Always against the LIVE container: a breakpoint
|
|
201
|
+
* rebuild between park and reveal replaces the one GL handed us (and its
|
|
202
|
+
* Destroy fires TabClosed, which drops the stale entry anyway).
|
|
203
|
+
*/
|
|
204
|
+
private flushPendingRecordShows;
|
|
191
205
|
/** Focus a records-region tab without feeding back into SetActiveTab loops */
|
|
192
206
|
private focusRecordsTab;
|
|
193
207
|
/**
|
|
@@ -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,EAMnB,MAAM,qCAAqC,CAAC;AAM7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAGrF,OAAO,EAAyB,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAIrE;;;;;;;;;GASG;AACH,qBAOa,qBAAsB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS,EAAE,aAAa;
|
|
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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"}
|
|
@@ -188,6 +188,15 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
188
188
|
// When a tab's content changes while active, both the reload path (workspace config subscription)
|
|
189
189
|
// and onTabShown can race to call loadTabContent, resulting in duplicate component rendering.
|
|
190
190
|
tabsCurrentlyLoading = new Set();
|
|
191
|
+
// Record-tab shows GL fired while the records region was HIDDEN, by tab id.
|
|
192
|
+
// Their content loads are deferred until the region is the visible surface —
|
|
193
|
+
// see the TabShown handler in wireRecordsLayoutEvents.
|
|
194
|
+
//
|
|
195
|
+
// A SET, not a map of containers: the replay deliberately re-resolves the live
|
|
196
|
+
// container via GetContainer(tabId), so a parked one would never be read — and
|
|
197
|
+
// holding it would retain a GL container, and its possibly-detached DOM, for as
|
|
198
|
+
// long as the region stays hidden. The type says the id is all that is kept.
|
|
199
|
+
pendingRecordShows = new Set();
|
|
191
200
|
// NEW: Smart component cache for preserving state across tab switches
|
|
192
201
|
cacheManager;
|
|
193
202
|
// Single-resource mode: render component directly without Golden Layout
|
|
@@ -420,6 +429,9 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
420
429
|
// Flush NOW — the visibility class must land in this pass, not
|
|
421
430
|
// whenever the next unrelated emission happens to run CD.
|
|
422
431
|
this.flushRegionCd();
|
|
432
|
+
if (showing) {
|
|
433
|
+
this.flushPendingRecordShows();
|
|
434
|
+
}
|
|
423
435
|
}
|
|
424
436
|
if (showing && activeTab && this.recordsLayoutInitialized) {
|
|
425
437
|
this.focusRecordsTab(activeTab.id);
|
|
@@ -536,6 +548,46 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
536
548
|
}
|
|
537
549
|
return true;
|
|
538
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Load a record tab's content into its GL container and mark it loaded —
|
|
553
|
+
* the second half of the TabShown handler, split out so a show parked
|
|
554
|
+
* while the region was hidden can be replayed on reveal.
|
|
555
|
+
*/
|
|
556
|
+
async loadShownRecordTab(tabId, container) {
|
|
557
|
+
await this.loadTabContent(tabId, container);
|
|
558
|
+
// Mark loaded ONLY when content actually attached to the LIVE
|
|
559
|
+
// container. During layout restore, GL can re-render item elements
|
|
560
|
+
// while an async load is in flight — the load appends into a
|
|
561
|
+
// detached element and the visible pane stays blank. Leaving the
|
|
562
|
+
// tab unmarked lets the next show retry, which hits the component
|
|
563
|
+
// cache and reattaches instantly into the live element.
|
|
564
|
+
const live = this.recordsLayoutManager.GetContainer(tabId);
|
|
565
|
+
if (live?.element && live.element.childElementCount > 0) {
|
|
566
|
+
this.recordsLayoutManager.MarkTabLoaded(tabId);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Replay the shows parked while the records region was hidden (see the
|
|
571
|
+
* TabShown handler). Always against the LIVE container: a breakpoint
|
|
572
|
+
* rebuild between park and reveal replaces the one GL handed us (and its
|
|
573
|
+
* Destroy fires TabClosed, which drops the stale entry anyway).
|
|
574
|
+
*/
|
|
575
|
+
flushPendingRecordShows() {
|
|
576
|
+
if (this.pendingRecordShows.size === 0) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const parked = [...this.pendingRecordShows];
|
|
580
|
+
this.pendingRecordShows.clear();
|
|
581
|
+
for (const tabId of parked) {
|
|
582
|
+
if (this.recordsLayoutManager.IsTabLoaded(tabId)) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
const live = this.recordsLayoutManager.GetContainer(tabId);
|
|
586
|
+
if (live) {
|
|
587
|
+
this.loadShownRecordTab(tabId, live).catch(e => LogError(`Deferred record tab load failed for ${tabId}: ${e}`));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
539
591
|
/** Focus a records-region tab without feeding back into SetActiveTab loops */
|
|
540
592
|
focusRecordsTab(tabId) {
|
|
541
593
|
if (this.recordsRegionActiveTabId !== tabId) {
|
|
@@ -781,20 +833,28 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
781
833
|
*/
|
|
782
834
|
wireRecordsLayoutEvents() {
|
|
783
835
|
this.subscriptions.push(this.recordsLayoutManager.TabShown.subscribe(async (event) => {
|
|
784
|
-
if (event.isFirstShow) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
836
|
+
if (!event.isFirstShow) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
// LAZY content. The records GL initializes EAGERLY (see
|
|
840
|
+
// ensureRecordsLayoutInitialized) so the strip and the pill never
|
|
841
|
+
// drift from the workspace — and as it builds, GL fires 'show' for
|
|
842
|
+
// every stack's active tab while the region is still
|
|
843
|
+
// visibility-hidden behind the main surface. Loading content on
|
|
844
|
+
// those shows means every restored record hydrates at BOOT: an open
|
|
845
|
+
// AI Agent Run pulls its whole run tree (every prompt run, action
|
|
846
|
+
// log, step) before the user has looked at it, and client-side
|
|
847
|
+
// RunView coalescing folds the ACTIVE tab's own reads into the same
|
|
848
|
+
// requests, so the surface the user is looking at waits for records
|
|
849
|
+
// they are not. Park the show; it is replayed when the region shows.
|
|
850
|
+
if (!this.ShowRecordsRegion) {
|
|
851
|
+
this.pendingRecordShows.add(event.tabId);
|
|
852
|
+
return;
|
|
796
853
|
}
|
|
854
|
+
this.pendingRecordShows.delete(event.tabId);
|
|
855
|
+
await this.loadShownRecordTab(event.tabId, event.container);
|
|
797
856
|
}), this.recordsLayoutManager.TabClosed.subscribe(async (tabId) => {
|
|
857
|
+
this.pendingRecordShows.delete(tabId);
|
|
798
858
|
this.cleanupTabComponent(tabId);
|
|
799
859
|
// REBUILD guard (breakpoint crossing): Destroy() fires TabClosed for
|
|
800
860
|
// every pane. The tabs are NOT closing — the layout is being rebuilt
|