@memberjunction/ng-explorer-core 5.21.0 → 5.23.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 (105) hide show
  1. package/dist/generated/lazy-feature-config.d.ts +19 -0
  2. package/dist/generated/lazy-feature-config.d.ts.map +1 -0
  3. package/dist/generated/lazy-feature-config.js +144 -0
  4. package/dist/generated/lazy-feature-config.js.map +1 -0
  5. package/dist/lib/command-palette/command-palette.component.d.ts +10 -1
  6. package/dist/lib/command-palette/command-palette.component.d.ts.map +1 -1
  7. package/dist/lib/command-palette/command-palette.component.js +68 -16
  8. package/dist/lib/command-palette/command-palette.component.js.map +1 -1
  9. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +49 -49
  10. package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
  11. package/dist/lib/generic/form-toolbar.js +10 -10
  12. package/dist/lib/generic/form-toolbar.js.map +1 -1
  13. package/dist/lib/generic/resource-container-component.d.ts +0 -1
  14. package/dist/lib/generic/resource-container-component.d.ts.map +1 -1
  15. package/dist/lib/generic/resource-container-component.js +3 -12
  16. package/dist/lib/generic/resource-container-component.js.map +1 -1
  17. package/dist/lib/oauth/oauth-callback.component.js +6 -6
  18. package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
  19. package/dist/lib/oauth/oauth.module.d.ts +2 -3
  20. package/dist/lib/oauth/oauth.module.d.ts.map +1 -1
  21. package/dist/lib/oauth/oauth.module.js +0 -4
  22. package/dist/lib/oauth/oauth.module.js.map +1 -1
  23. package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +8 -23
  24. package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
  25. package/dist/lib/resource-wrappers/chat-collections-resource.component.js +68 -117
  26. package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
  27. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +9 -21
  28. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
  29. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +66 -137
  30. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
  31. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +3 -19
  32. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
  33. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +16 -98
  34. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
  35. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +0 -1
  36. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
  37. package/dist/lib/resource-wrappers/dashboard-resource.component.js +4 -12
  38. package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
  39. package/dist/lib/resource-wrappers/view-resource.component.d.ts +13 -11
  40. package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
  41. package/dist/lib/resource-wrappers/view-resource.component.js +80 -89
  42. package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
  43. package/dist/lib/services/lazy-module-registry.d.ts +24 -9
  44. package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
  45. package/dist/lib/services/lazy-module-registry.js +32 -13
  46. package/dist/lib/services/lazy-module-registry.js.map +1 -1
  47. package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
  48. package/dist/lib/shell/components/header/app-nav.component.js +18 -3
  49. package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
  50. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +38 -16
  51. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
  52. package/dist/lib/shell/components/tabs/component-cache-manager.js +57 -35
  53. package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
  54. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +56 -1
  55. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  56. package/dist/lib/shell/components/tabs/tab-container.component.js +298 -53
  57. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  58. package/dist/lib/shell/services/settings-dialog.service.d.ts +8 -8
  59. package/dist/lib/shell/services/settings-dialog.service.d.ts.map +1 -1
  60. package/dist/lib/shell/services/settings-dialog.service.js +20 -26
  61. package/dist/lib/shell/services/settings-dialog.service.js.map +1 -1
  62. package/dist/lib/shell/shell.component.d.ts +26 -2
  63. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  64. package/dist/lib/shell/shell.component.js +225 -54
  65. package/dist/lib/shell/shell.component.js.map +1 -1
  66. package/dist/lib/shell/shell.module.d.ts +4 -5
  67. package/dist/lib/shell/shell.module.d.ts.map +1 -1
  68. package/dist/lib/shell/shell.module.js +4 -8
  69. package/dist/lib/shell/shell.module.js.map +1 -1
  70. package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +72 -71
  71. package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
  72. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js +11 -11
  73. package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js.map +1 -1
  74. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts +36 -12
  75. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts.map +1 -1
  76. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js +78 -50
  77. package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js.map +1 -1
  78. package/dist/lib/single-dashboard/single-dashboard.component.d.ts +12 -5
  79. package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
  80. package/dist/lib/single-dashboard/single-dashboard.component.js +44 -55
  81. package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
  82. package/dist/lib/single-list-detail/single-list-detail.component.d.ts +10 -2
  83. package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
  84. package/dist/lib/single-list-detail/single-list-detail.component.js +313 -243
  85. package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
  86. package/dist/lib/user-menu/base-user-menu.d.ts +4 -0
  87. package/dist/lib/user-menu/base-user-menu.d.ts.map +1 -1
  88. package/dist/lib/user-menu/base-user-menu.js +26 -0
  89. package/dist/lib/user-menu/base-user-menu.js.map +1 -1
  90. package/dist/lib/user-menu/user-menu.types.d.ts +20 -0
  91. package/dist/lib/user-menu/user-menu.types.d.ts.map +1 -1
  92. package/dist/lib/user-menu/user-menu.types.js.map +1 -1
  93. package/dist/module.d.ts +23 -34
  94. package/dist/module.d.ts.map +1 -1
  95. package/dist/module.js +33 -74
  96. package/dist/module.js.map +1 -1
  97. package/dist/public-api.d.ts +1 -1
  98. package/dist/public-api.d.ts.map +1 -1
  99. package/dist/public-api.js +1 -1
  100. package/dist/public-api.js.map +1 -1
  101. package/package.json +38 -47
  102. package/dist/lib/services/lazy-feature-config.d.ts +0 -16
  103. package/dist/lib/services/lazy-feature-config.d.ts.map +0 -1
  104. package/dist/lib/services/lazy-feature-config.js +0 -113
  105. package/dist/lib/services/lazy-feature-config.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { Component, ViewChild, createComponent, ViewEncapsulation, HostListener, Output, EventEmitter, inject } from '@angular/core';
2
2
  import { MJGlobal } from '@memberjunction/global';
3
- import { BaseResourceComponent } from '@memberjunction/ng-shared';
3
+ import { BaseResourceComponent, HomeAppPinService } from '@memberjunction/ng-shared';
4
4
  import { ResourceData, ResourcePermissionEngine } from '@memberjunction/core-entities';
5
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
5
6
  import { LogError, Metadata } from '@memberjunction/core';
6
7
  import { ComponentCacheManager } from './component-cache-manager';
7
- import { LazyModuleRegistry } from '../../../services/lazy-module-registry';
8
8
  import * as i0 from "@angular/core";
9
9
  import * as i1 from "@memberjunction/ng-base-application";
10
10
  const _c0 = ["glContainer"];
@@ -15,6 +15,19 @@ function TabContainerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
15
15
  function TabContainerComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
16
16
  i0.ɵɵelement(0, "div", 4, 1);
17
17
  } }
18
+ function TabContainerComponent_Conditional_3_Conditional_8_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵelementStart(0, "span");
20
+ i0.ɵɵtext(1, "Pinned to Home");
21
+ i0.ɵɵelementEnd();
22
+ i0.ɵɵelementStart(2, "span", 14);
23
+ i0.ɵɵelement(3, "i", 15);
24
+ i0.ɵɵelementEnd();
25
+ } }
26
+ function TabContainerComponent_Conditional_3_Conditional_9_Template(rf, ctx) { if (rf & 1) {
27
+ i0.ɵɵelementStart(0, "span");
28
+ i0.ɵɵtext(1, "Pin to Home");
29
+ i0.ɵɵelementEnd();
30
+ } }
18
31
  function TabContainerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
19
32
  const _r1 = i0.ɵɵgetCurrentView();
20
33
  i0.ɵɵelementStart(0, "div", 6)(1, "div", 7);
@@ -25,28 +38,38 @@ function TabContainerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
25
38
  i0.ɵɵelementEnd()();
26
39
  i0.ɵɵelement(5, "div", 9);
27
40
  i0.ɵɵelementStart(6, "div", 7);
28
- i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextClose()); });
41
+ i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextPinToHome()); });
29
42
  i0.ɵɵelement(7, "i", 10);
30
- i0.ɵɵelementStart(8, "span");
31
- i0.ɵɵtext(9, "Close Tab");
43
+ i0.ɵɵconditionalCreate(8, TabContainerComponent_Conditional_3_Conditional_8_Template, 4, 0)(9, TabContainerComponent_Conditional_3_Conditional_9_Template, 2, 0, "span");
44
+ i0.ɵɵelementEnd();
45
+ i0.ɵɵelement(10, "div", 9);
46
+ i0.ɵɵelementStart(11, "div", 7);
47
+ i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_11_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextClose()); });
48
+ i0.ɵɵelement(12, "i", 11);
49
+ i0.ɵɵelementStart(13, "span");
50
+ i0.ɵɵtext(14, "Close Tab");
32
51
  i0.ɵɵelementEnd()();
33
- i0.ɵɵelementStart(10, "div", 7);
34
- i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_10_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseOthers()); });
35
- i0.ɵɵelement(11, "i", 11);
36
- i0.ɵɵelementStart(12, "span");
37
- i0.ɵɵtext(13, "Close Others");
52
+ i0.ɵɵelementStart(15, "div", 7);
53
+ i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_15_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseOthers()); });
54
+ i0.ɵɵelement(16, "i", 12);
55
+ i0.ɵɵelementStart(17, "span");
56
+ i0.ɵɵtext(18, "Close Others");
38
57
  i0.ɵɵelementEnd()();
39
- i0.ɵɵelementStart(14, "div", 7);
40
- i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_14_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseToRight()); });
41
- i0.ɵɵelement(15, "i", 12);
42
- i0.ɵɵelementStart(16, "span");
43
- i0.ɵɵtext(17, "Close to Right");
58
+ i0.ɵɵelementStart(19, "div", 7);
59
+ i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_19_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseToRight()); });
60
+ i0.ɵɵelement(20, "i", 13);
61
+ i0.ɵɵelementStart(21, "span");
62
+ i0.ɵɵtext(22, "Close to Right");
44
63
  i0.ɵɵelementEnd()()();
45
64
  } if (rf & 2) {
46
65
  const ctx_r1 = i0.ɵɵnextContext();
47
66
  i0.ɵɵstyleProp("left", ctx_r1.contextMenuX, "px")("top", ctx_r1.contextMenuY, "px");
48
67
  i0.ɵɵadvance(4);
49
68
  i0.ɵɵtextInterpolate(ctx_r1.isContextTabPinned ? "Unpin Tab" : "Pin Tab");
69
+ i0.ɵɵadvance(2);
70
+ i0.ɵɵclassProp("new-item", !ctx_r1.isContextTabPinnedToHome)("disabled", ctx_r1.isContextTabPinnedToHome);
71
+ i0.ɵɵadvance(2);
72
+ i0.ɵɵconditional(ctx_r1.isContextTabPinnedToHome ? 8 : 9);
50
73
  } }
51
74
  /**
52
75
  * Container for Golden Layout tabs with app-colored styling.
@@ -78,7 +101,7 @@ export class TabContainerComponent {
78
101
  * The shell can use this to show an error dialog and redirect.
79
102
  */
80
103
  layoutInitError = new EventEmitter();
81
- lazyRegistry = inject(LazyModuleRegistry);
104
+ pinService = inject(HomeAppPinService);
82
105
  subscriptions = [];
83
106
  layoutInitRetryCount = 0;
84
107
  MAX_LAYOUT_INIT_RETRIES = 5;
@@ -86,12 +109,18 @@ export class TabContainerComponent {
86
109
  layoutRestorationComplete = false; // True only AFTER layout is fully restored/created
87
110
  // Track component references for cleanup (legacy - keep for backward compat during transition)
88
111
  componentRefs = new Map();
112
+ // Guard against concurrent loadTabContent calls for the same tab.
113
+ // When a tab's content changes while active, both the reload path (workspace config subscription)
114
+ // and onTabShown can race to call loadTabContent, resulting in duplicate component rendering.
115
+ tabsCurrentlyLoading = new Set();
89
116
  // NEW: Smart component cache for preserving state across tab switches
90
117
  cacheManager;
91
118
  // Single-resource mode: render component directly without Golden Layout
92
119
  // This avoids the 20px height issue when GL header is hidden
93
120
  useSingleResourceMode = false;
94
121
  singleResourceComponentRef = null;
122
+ /** Cache identity of the current single-resource component for detachment */
123
+ singleResourceCacheIdentity = null;
95
124
  previousTabBarVisible = null;
96
125
  currentSingleResourceSignature = null; // Track loaded content signature to avoid unnecessary reloads
97
126
  isCreatingInitialTabs = false; // Flag to prevent syncTabsWithConfiguration during initial tab creation
@@ -139,8 +168,17 @@ export class TabContainerComponent {
139
168
  if (activeTab) {
140
169
  const signature = this.getTabContentSignature(activeTab);
141
170
  if (signature !== this.currentSingleResourceSignature) {
171
+ // DO NOT call saveCurrentComponentQueryParams() here — by the time this
172
+ // subscription fires, OpenTab has already replaced the tab config with the
173
+ // new nav item's config, so queryParams are gone. The cache entry already
174
+ // has the correct queryParams from the most recent unchanged-signature save.
142
175
  this.loadSingleResourceContent();
143
176
  }
177
+ else {
178
+ // Signature unchanged — sync queryParams to cache entry so it stays current.
179
+ // This catches incremental queryParam updates (e.g., user selects a conversation).
180
+ this.saveCurrentComponentQueryParams();
181
+ }
144
182
  }
145
183
  }
146
184
  else if (this.layoutRestorationComplete && !this.isCreatingInitialTabs) {
@@ -304,7 +342,9 @@ export class TabContainerComponent {
304
342
  const shouldUseSingleResourceMode = !tabBarVisible;
305
343
  if (shouldUseSingleResourceMode !== this.useSingleResourceMode) {
306
344
  this.useSingleResourceMode = shouldUseSingleResourceMode;
307
- this.cdr.detectChanges();
345
+ // Defer detectChanges to next microtask to avoid ExpressionChangedAfterItHasBeenCheckedError
346
+ // when this handler fires during an already-running change detection cycle.
347
+ Promise.resolve().then(() => this.cdr.detectChanges());
308
348
  if (this.useSingleResourceMode) {
309
349
  // Transitioning to single-resource mode
310
350
  // **CRITICAL FIX**: Wait for the template to render directContentContainer
@@ -403,23 +443,30 @@ export class TabContainerComponent {
403
443
  if (cached) {
404
444
  // Clean up previous single-resource component (if different)
405
445
  this.cleanupSingleResourceComponent();
406
- // Detach from tab tracking (it was attached to a tab in Golden Layout)
407
- this.cacheManager.markAsDetached(activeTab.id);
446
+ // Mark cached component as attached to this tab (it was detached / available for reuse).
447
+ // IMPORTANT: We use markAsAttached here, NOT markAsDetached — the component is being
448
+ // reattached to the DOM and should NOT be eligible for LRU eviction.
449
+ this.cacheManager.markAsAttached(driverClass, resourceData.ResourceRecordID || '', activeTab.applicationId, activeTab.id);
408
450
  // Reattach the cached wrapper element to single-resource container
409
451
  cached.wrapperElement.style.height = "100%"; // Ensure full height
410
452
  container.appendChild(cached.wrapperElement);
411
- // Store reference for cleanup
453
+ // Store reference and identity for cleanup/detachment
412
454
  this.singleResourceComponentRef = cached.componentRef;
413
- return;
414
- }
415
- // Get the component registration (with lazy loading fallback)
416
- let resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
417
- if (!resourceReg) {
418
- const loaded = await this.lazyRegistry.Load(driverClass);
419
- if (loaded) {
420
- resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
455
+ this.singleResourceCacheIdentity = { driverClass, recordId: resourceData.ResourceRecordID || '', appId: activeTab.applicationId, tabId: activeTab.id };
456
+ // Restore saved queryParams to the tab config so the URL reflects
457
+ // the component's preserved state (e.g., selected conversation, collection drill-down).
458
+ if (cached.savedQueryParams) {
459
+ this.workspaceManager.UpdateTabConfiguration(activeTab.id, {
460
+ queryParams: cached.savedQueryParams
461
+ });
462
+ // Do NOT clear savedQueryParams here — the else branch (unchanged-signature saves)
463
+ // will keep it current while the component is active. Clearing it would cause the
464
+ // queryParams to be lost on the next detach/reattach cycle.
421
465
  }
466
+ return;
422
467
  }
468
+ // Get the component registration (with lazy loading fallback via ClassFactory)
469
+ const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
423
470
  if (!resourceReg) {
424
471
  LogError(`Unable to find resource registration for driver class: ${driverClass}`);
425
472
  return;
@@ -439,6 +486,18 @@ export class TabContainerComponent {
439
486
  instance.LoadCompleteEvent = () => {
440
487
  this.emitFirstLoadCompleteOnce();
441
488
  };
489
+ // Wire up display name change for single-resource mode.
490
+ // Guard: only update the title if THIS component is the currently displayed one.
491
+ // Without this guard, cached components (detached but alive) can fire this callback
492
+ // and overwrite the active tab's title with a stale name.
493
+ instance.DisplayNameChangedEvent = (newName) => {
494
+ if (this.singleResourceComponentRef?.instance === instance) {
495
+ const tabId = this.workspaceManager.GetActiveTabId();
496
+ if (tabId) {
497
+ this.workspaceManager.UpdateTabTitle(tabId, newName);
498
+ }
499
+ }
500
+ };
442
501
  // Get the native element and append to container
443
502
  const nativeElement = componentRef.hostView.rootNodes[0];
444
503
  container.appendChild(nativeElement);
@@ -446,22 +505,76 @@ export class TabContainerComponent {
446
505
  if (container.children?.length > 0) {
447
506
  container.children[0].style.height = "100%";
448
507
  }
449
- // Store reference for cleanup
508
+ // Cache the component for reuse when switching between nav items within the same app.
509
+ // Without this, every nav switch creates a brand new component from scratch.
510
+ const wrapperElement = nativeElement;
511
+ this.cacheManager.cacheComponent(componentRef, wrapperElement, resourceData, activeTab.id);
512
+ // Store reference and identity for cleanup/detachment
450
513
  this.singleResourceComponentRef = componentRef;
514
+ this.singleResourceCacheIdentity = { driverClass, recordId: resourceData.ResourceRecordID || '', appId: activeTab.applicationId, tabId: activeTab.id };
451
515
  }
452
516
  /**
453
517
  * Clean up single-resource mode component
454
518
  */
519
+ /**
520
+ * Detaches the current single-resource component from the DOM and marks it as
521
+ * available for reuse in the component cache.
522
+ *
523
+ * ╔══════════════════════════════════════════════════════════════════════════╗
524
+ * ║ ⚠️ DO NOT DESTROY THE COMPONENT HERE — INTENTIONAL DESIGN CHOICE ⚠️ ║
525
+ * ║ ║
526
+ * ║ The component is DETACHED from the DOM, NOT destroyed. It stays alive ║
527
+ * ║ in the ComponentCacheManager with its full Angular state preserved ║
528
+ * ║ (properties, subscriptions, loaded data, scroll position, etc). ║
529
+ * ║ ║
530
+ * ║ When the user returns to this tab, the cached component is reattached ║
531
+ * ║ instantly — no data reload, no API calls, no flash of empty content. ║
532
+ * ║ ║
533
+ * ║ Destroying components here "for memory optimization" is a net ║
534
+ * ║ NEGATIVE: the reload on return is far more expensive (DB queries, ║
535
+ * ║ API calls, re-rendering) than keeping the component in memory. ║
536
+ * ║ The LRU eviction in ComponentCacheManager handles memory limits — ║
537
+ * ║ when MaxDetachedComponents is exceeded, the LEAST recently used ║
538
+ * ║ components are evicted automatically. ║
539
+ * ║ ║
540
+ * ║ If you think memory is a problem, adjust MaxDetachedComponents ║
541
+ * ║ instead of destroying components here. ║
542
+ * ╚══════════════════════════════════════════════════════════════════════════╝
543
+ */
544
+ /**
545
+ * Save the currently displayed component's queryParams to its cache entry.
546
+ * Called on every config change so the cache entry always has the latest queryParams,
547
+ * even after the tab config is overwritten by a new nav item.
548
+ */
549
+ saveCurrentComponentQueryParams() {
550
+ if (!this.singleResourceCacheIdentity)
551
+ return;
552
+ const { tabId } = this.singleResourceCacheIdentity;
553
+ const tab = this.workspaceManager.GetTab(tabId);
554
+ const qp = tab?.configuration?.['queryParams'];
555
+ const cached = this.cacheManager.getComponentByTabId(tabId);
556
+ if (cached) {
557
+ cached.savedQueryParams = (qp && Object.keys(qp).length > 0) ? { ...qp } : undefined;
558
+ }
559
+ }
455
560
  cleanupSingleResourceComponent() {
456
561
  if (this.singleResourceComponentRef) {
457
- this.appRef.detachView(this.singleResourceComponentRef.hostView);
458
- this.singleResourceComponentRef.destroy();
562
+ if (this.singleResourceCacheIdentity) {
563
+ const { driverClass, recordId, appId } = this.singleResourceCacheIdentity;
564
+ // Mark as DETACHED by resource identity — the ONE consistent key used everywhere.
565
+ this.cacheManager.markAsDetached(driverClass, recordId, appId);
566
+ }
459
567
  this.singleResourceComponentRef = null;
568
+ this.singleResourceCacheIdentity = null;
460
569
  }
461
- // Clear the container
570
+ // Remove children from the container. This detaches the wrapper DOM element
571
+ // without destroying the Angular component — it lives on in the cache.
572
+ // Using removeChild (not innerHTML='') to avoid aggressive DOM cleanup.
462
573
  const container = this.directContentContainer?.nativeElement;
463
574
  if (container) {
464
- container.innerHTML = '';
575
+ while (container.firstChild) {
576
+ container.removeChild(container.firstChild);
577
+ }
465
578
  }
466
579
  }
467
580
  /**
@@ -517,6 +630,13 @@ export class TabContainerComponent {
517
630
  * Uses component cache to reuse components for same resources
518
631
  */
519
632
  async loadTabContent(tabId, container) {
633
+ // Per-tab guard: prevent concurrent loads of the same tab content.
634
+ // This can happen when a tab's content changes while active — both the workspace
635
+ // config subscription reload path and onTabShown can race to call this method.
636
+ if (this.tabsCurrentlyLoading.has(tabId)) {
637
+ return;
638
+ }
639
+ this.tabsCurrentlyLoading.add(tabId);
520
640
  try {
521
641
  const tab = this.workspaceManager.GetTab(tabId);
522
642
  if (!tab) {
@@ -555,14 +675,8 @@ export class TabContainerComponent {
555
675
  }
556
676
  return;
557
677
  }
558
- // Get the component registration using the driver class (with lazy loading fallback)
559
- let resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
560
- if (!resourceReg) {
561
- const loaded = await this.lazyRegistry.Load(driverClass);
562
- if (loaded) {
563
- resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
564
- }
565
- }
678
+ // Get the component registration using the driver class (with lazy loading fallback via ClassFactory)
679
+ const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
566
680
  if (!resourceReg) {
567
681
  LogError(`Unable to find resource registration for driver class: ${driverClass}`);
568
682
  return;
@@ -588,6 +702,11 @@ export class TabContainerComponent {
588
702
  // TODO: Implement UpdateTabTitle in WorkspaceStateManager
589
703
  }
590
704
  };
705
+ // Wire up display name change notifications
706
+ instance.DisplayNameChangedEvent = (newName) => {
707
+ this.layoutManager.UpdateTabStyle(tabId, { title: newName });
708
+ this.workspaceManager.UpdateTabTitle(tabId, newName);
709
+ };
591
710
  // Create a container div for the component
592
711
  const componentElement = document.createElement('div');
593
712
  componentElement.className = 'tab-content-wrapper';
@@ -605,6 +724,9 @@ export class TabContainerComponent {
605
724
  catch (e) {
606
725
  LogError(e);
607
726
  }
727
+ finally {
728
+ this.tabsCurrentlyLoading.delete(tabId);
729
+ }
608
730
  }
609
731
  /**
610
732
  * Update tab display name in background without loading full component
@@ -624,13 +746,7 @@ export class TabContainerComponent {
624
746
  }
625
747
  // Get the resource registration to access GetResourceDisplayName without loading full component
626
748
  const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
627
- let resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
628
- if (!resourceReg) {
629
- const loaded = await this.lazyRegistry.Load(driverClass);
630
- if (loaded) {
631
- resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
632
- }
633
- }
749
+ const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
634
750
  if (!resourceReg) {
635
751
  return;
636
752
  }
@@ -809,7 +925,7 @@ export class TabContainerComponent {
809
925
  */
810
926
  cleanupTabComponent(tabId) {
811
927
  // First, try to detach from cache (preserves component for reuse)
812
- const cachedInfo = this.cacheManager.markAsDetached(tabId);
928
+ const cachedInfo = this.cacheManager.findAndDetachByTabId(tabId);
813
929
  if (cachedInfo) {
814
930
  // Remove from legacy componentRefs but keep in cache
815
931
  this.componentRefs.delete(tabId);
@@ -973,6 +1089,135 @@ export class TabContainerComponent {
973
1089
  }
974
1090
  this.hideContextMenu();
975
1091
  }
1092
+ /**
1093
+ * Check if context menu tab is pinned to Home dashboard
1094
+ */
1095
+ get isContextTabPinnedToHome() {
1096
+ if (!this.contextMenuTabId)
1097
+ return false;
1098
+ const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
1099
+ if (!tab)
1100
+ return false;
1101
+ const resourceType = this.resolveResourceType(tab);
1102
+ return this.pinService.IsPinned(resourceType, tab.configuration);
1103
+ }
1104
+ /**
1105
+ * Pin current context menu tab to Home dashboard
1106
+ */
1107
+ async onContextPinToHome() {
1108
+ if (this.isContextTabPinnedToHome) {
1109
+ this.hideContextMenu();
1110
+ return;
1111
+ }
1112
+ if (!this.contextMenuTabId) {
1113
+ this.hideContextMenu();
1114
+ return;
1115
+ }
1116
+ const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
1117
+ if (!tab) {
1118
+ this.hideContextMenu();
1119
+ return;
1120
+ }
1121
+ const resourceType = this.resolveResourceType(tab);
1122
+ const activeApp = this.appManager.GetActiveApp();
1123
+ // Resolve nav item icon for Custom pins
1124
+ let pinIcon;
1125
+ if (resourceType === 'Custom' && activeApp) {
1126
+ const navItemName = tab.configuration?.['navItemName'];
1127
+ if (navItemName) {
1128
+ const navItems = await activeApp.GetNavItems();
1129
+ const navItem = navItems.find(ni => ni.Label === navItemName);
1130
+ pinIcon = navItem?.Icon || undefined;
1131
+ }
1132
+ }
1133
+ const added = this.pinService.AddPin({
1134
+ DisplayName: tab.title || 'Untitled',
1135
+ ResourceType: resourceType,
1136
+ ApplicationID: tab.applicationId || activeApp?.ID,
1137
+ ApplicationName: activeApp?.Name,
1138
+ Icon: pinIcon,
1139
+ Color: activeApp?.GetColor() || undefined,
1140
+ Configuration: tab.configuration,
1141
+ });
1142
+ if (added) {
1143
+ MJNotificationService.Instance.CreateSimpleNotification(`Pinned "${tab.title}" to Home`, 'success', 2000);
1144
+ this.captureContextTabThumbnail(tab);
1145
+ }
1146
+ else {
1147
+ MJNotificationService.Instance.CreateSimpleNotification(`"${tab.title}" is already pinned to Home`, 'info', 3000);
1148
+ }
1149
+ this.hideContextMenu();
1150
+ }
1151
+ /**
1152
+ * Resolve a WorkspaceTab's resource type string for pin matching
1153
+ */
1154
+ resolveResourceType(tab) {
1155
+ const config = tab.configuration;
1156
+ const rt = config.resourceType || '';
1157
+ if (rt === 'Dashboards' || config['dashboardId'])
1158
+ return 'Dashboards';
1159
+ if (rt === 'User Views' || rt === 'MJ: User Views' || config['viewId'])
1160
+ return 'User Views';
1161
+ if (rt === 'Queries' || config['queryId'])
1162
+ return 'Queries';
1163
+ if (rt === 'Reports' || config['reportId'])
1164
+ return 'Reports';
1165
+ if (rt === 'Records' || (config['entity'] && config['recordId']))
1166
+ return 'Records';
1167
+ if (rt === 'Custom' || config['navItemName'])
1168
+ return 'Custom';
1169
+ return rt || 'Custom';
1170
+ }
1171
+ /**
1172
+ * Capture thumbnail for a just-pinned tab (async, non-blocking)
1173
+ */
1174
+ async captureContextTabThumbnail(tab) {
1175
+ try {
1176
+ // Find the active content element — differs by mode
1177
+ let contentEl = null;
1178
+ if (this.useSingleResourceMode) {
1179
+ contentEl = this.directContentContainer?.nativeElement ?? null;
1180
+ }
1181
+ else {
1182
+ // In Golden Layout mode, find the active tab's content pane
1183
+ contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
1184
+ }
1185
+ if (!contentEl)
1186
+ return;
1187
+ const thumbnail = await this.pinService.CaptureThumbnail(contentEl);
1188
+ if (thumbnail) {
1189
+ const resourceType = this.resolveResourceType(tab);
1190
+ const pin = this.pinService.FindPin(resourceType, tab.configuration);
1191
+ if (pin) {
1192
+ this.pinService.UpdatePin(pin.Id, { Thumbnail: thumbnail });
1193
+ }
1194
+ }
1195
+ }
1196
+ catch {
1197
+ // Thumbnail capture is best-effort
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Public method for external callers (e.g. shell) to capture a thumbnail
1202
+ * of the currently visible content, regardless of mode.
1203
+ */
1204
+ async CaptureActiveThumbnail() {
1205
+ try {
1206
+ let contentEl = null;
1207
+ if (this.useSingleResourceMode) {
1208
+ contentEl = this.directContentContainer?.nativeElement ?? null;
1209
+ }
1210
+ else {
1211
+ contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
1212
+ }
1213
+ if (!contentEl)
1214
+ return undefined;
1215
+ return await this.pinService.CaptureThumbnail(contentEl);
1216
+ }
1217
+ catch {
1218
+ return undefined;
1219
+ }
1220
+ }
976
1221
  /**
977
1222
  * While the naming implies this is only invoked once, components we DO NOT CONTROL might have race
978
1223
  * conditions that result in unpredictable behavior. To avoid those causing loading screen overaly to show
@@ -990,21 +1235,21 @@ export class TabContainerComponent {
990
1235
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.directContentContainer = _t.first);
991
1236
  } }, hostBindings: function TabContainerComponent_HostBindings(rf, ctx) { if (rf & 1) {
992
1237
  i0.ɵɵlistener("resize", function TabContainerComponent_resize_HostBindingHandler() { return ctx.onWindowResize(); }, i0.ɵɵresolveWindow);
993
- } }, outputs: { firstResourceLoadComplete: "firstResourceLoadComplete", layoutInitError: "layoutInitError" }, standalone: false, decls: 4, vars: 2, consts: [["directContentContainer", ""], ["glContainer", ""], [1, "tab-container"], [1, "direct-content-container"], [1, "gl-container"], [1, "context-menu", 3, "left", "top"], [1, "context-menu"], [1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-thumbtack"], [1, "context-menu-divider"], [1, "fa-solid", "fa-xmark"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-angles-right"]], template: function TabContainerComponent_Template(rf, ctx) { if (rf & 1) {
1238
+ } }, outputs: { firstResourceLoadComplete: "firstResourceLoadComplete", layoutInitError: "layoutInitError" }, standalone: false, decls: 4, vars: 2, consts: [["directContentContainer", ""], ["glContainer", ""], [1, "tab-container"], [1, "direct-content-container"], [1, "gl-container"], [1, "context-menu", 3, "left", "top"], [1, "context-menu"], [1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-thumbtack"], [1, "context-menu-divider"], [1, "fa-solid", "fa-house-chimney"], [1, "fa-solid", "fa-xmark"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-angles-right"], [1, "check"], [1, "fa-solid", "fa-check"]], template: function TabContainerComponent_Template(rf, ctx) { if (rf & 1) {
994
1239
  i0.ɵɵelementStart(0, "div", 2);
995
1240
  i0.ɵɵconditionalCreate(1, TabContainerComponent_Conditional_1_Template, 2, 0, "div", 3)(2, TabContainerComponent_Conditional_2_Template, 2, 0, "div", 4);
996
- i0.ɵɵconditionalCreate(3, TabContainerComponent_Conditional_3_Template, 18, 5, "div", 5);
1241
+ i0.ɵɵconditionalCreate(3, TabContainerComponent_Conditional_3_Template, 23, 10, "div", 5);
997
1242
  i0.ɵɵelementEnd();
998
1243
  } if (rf & 2) {
999
1244
  i0.ɵɵadvance();
1000
1245
  i0.ɵɵconditional(ctx.useSingleResourceMode ? 1 : 2);
1001
1246
  i0.ɵɵadvance(2);
1002
1247
  i0.ɵɵconditional(ctx.contextMenuVisible ? 3 : -1);
1003
- } }, styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"], encapsulation: 2 });
1248
+ } }, styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Context menu item variants */\n.context-menu-item.new-item i { color: var(--mj-brand-primary); }\n.context-menu-item.new-item { font-weight: 500; }\n.context-menu-item.disabled { color: var(--mj-text-disabled); cursor: default; }\n.context-menu-item.disabled:hover { background: transparent; }\n.context-menu-item.disabled i { color: var(--mj-text-disabled); }\n.context-menu-item .check { margin-left: auto; color: var(--mj-status-success); font-size: 12px; }\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"], encapsulation: 2 });
1004
1249
  }
1005
1250
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TabContainerComponent, [{
1006
1251
  type: Component,
1007
- args: [{ standalone: false, selector: 'mj-tab-container', encapsulation: ViewEncapsulation.None, template: "<div class=\"tab-container\">\n\n <!-- Single-Resource Mode: Direct component rendering without Golden Layout -->\n <!-- This avoids the 20px height issue when GL header is hidden -->\n @if (useSingleResourceMode) {\n <div #directContentContainer class=\"direct-content-container\"></div>\n } @else {\n <!-- Multi-Tab Mode: Golden Layout Container -->\n <div #glContainer class=\"gl-container\"></div>\n }\n\n <!-- Context Menu (only relevant in multi-tab mode) -->\n @if (contextMenuVisible) {\n <div\n class=\"context-menu\"\n [style.left.px]=\"contextMenuX\"\n [style.top.px]=\"contextMenuY\">\n <div class=\"context-menu-item\" (click)=\"onContextPin()\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n <span>{{ isContextTabPinned ? 'Unpin Tab' : 'Pin Tab' }}</span>\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" (click)=\"onContextClose()\">\n <i class=\"fa-solid fa-xmark\"></i>\n <span>Close Tab</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseOthers()\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>Close Others</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseToRight()\">\n <i class=\"fa-solid fa-angles-right\"></i>\n <span>Close to Right</span>\n </div>\n </div>\n }\n</div>\n", styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"] }]
1252
+ args: [{ standalone: false, selector: 'mj-tab-container', encapsulation: ViewEncapsulation.None, template: "<div class=\"tab-container\">\n\n <!-- Single-Resource Mode: Direct component rendering without Golden Layout -->\n <!-- This avoids the 20px height issue when GL header is hidden -->\n @if (useSingleResourceMode) {\n <div #directContentContainer class=\"direct-content-container\"></div>\n } @else {\n <!-- Multi-Tab Mode: Golden Layout Container -->\n <div #glContainer class=\"gl-container\"></div>\n }\n\n <!-- Context Menu (only relevant in multi-tab mode) -->\n @if (contextMenuVisible) {\n <div\n class=\"context-menu\"\n [style.left.px]=\"contextMenuX\"\n [style.top.px]=\"contextMenuY\">\n <div class=\"context-menu-item\" (click)=\"onContextPin()\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n <span>{{ isContextTabPinned ? 'Unpin Tab' : 'Pin Tab' }}</span>\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" [class.new-item]=\"!isContextTabPinnedToHome\" [class.disabled]=\"isContextTabPinnedToHome\" (click)=\"onContextPinToHome()\">\n <i class=\"fa-solid fa-house-chimney\"></i>\n @if (isContextTabPinnedToHome) {\n <span>Pinned to Home</span>\n <span class=\"check\"><i class=\"fa-solid fa-check\"></i></span>\n } @else {\n <span>Pin to Home</span>\n }\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" (click)=\"onContextClose()\">\n <i class=\"fa-solid fa-xmark\"></i>\n <span>Close Tab</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseOthers()\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>Close Others</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseToRight()\">\n <i class=\"fa-solid fa-angles-right\"></i>\n <span>Close to Right</span>\n </div>\n </div>\n }\n</div>\n", styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Context menu item variants */\n.context-menu-item.new-item i { color: var(--mj-brand-primary); }\n.context-menu-item.new-item { font-weight: 500; }\n.context-menu-item.disabled { color: var(--mj-text-disabled); cursor: default; }\n.context-menu-item.disabled:hover { background: transparent; }\n.context-menu-item.disabled i { color: var(--mj-text-disabled); }\n.context-menu-item .check { margin-left: auto; color: var(--mj-status-success); font-size: 12px; }\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"] }]
1008
1253
  }], () => [{ type: i1.GoldenLayoutManager }, { type: i1.WorkspaceStateManager }, { type: i1.ApplicationManager }, { type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }, { type: i0.ChangeDetectorRef }], { glContainer: [{
1009
1254
  type: ViewChild,
1010
1255
  args: ['glContainer', { static: false }]