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

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 (138) hide show
  1. package/README.md +10 -0
  2. package/dist/app-routing.module.d.ts.map +1 -1
  3. package/dist/app-routing.module.js +13 -6
  4. package/dist/app-routing.module.js.map +1 -1
  5. package/dist/generated/lazy-feature-config.d.ts +1 -1
  6. package/dist/generated/lazy-feature-config.d.ts.map +1 -1
  7. package/dist/generated/lazy-feature-config.js +4 -2
  8. package/dist/generated/lazy-feature-config.js.map +1 -1
  9. package/dist/lib/guards/moved-nav-item-redirect.d.ts +15 -0
  10. package/dist/lib/guards/moved-nav-item-redirect.d.ts.map +1 -0
  11. package/dist/lib/guards/moved-nav-item-redirect.js +37 -0
  12. package/dist/lib/guards/moved-nav-item-redirect.js.map +1 -0
  13. package/dist/lib/oauth/oauth-callback.component.d.ts +2 -0
  14. package/dist/lib/oauth/oauth-callback.component.d.ts.map +1 -1
  15. package/dist/lib/oauth/oauth-callback.component.js +10 -10
  16. package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
  17. package/dist/lib/omnibar/omnibar-dashboard-matches.d.ts +34 -0
  18. package/dist/lib/omnibar/omnibar-dashboard-matches.d.ts.map +1 -0
  19. package/dist/lib/omnibar/omnibar-dashboard-matches.js +70 -0
  20. package/dist/lib/omnibar/omnibar-dashboard-matches.js.map +1 -0
  21. package/dist/lib/omnibar/omnibar-palette.component.d.ts +27 -2
  22. package/dist/lib/omnibar/omnibar-palette.component.d.ts.map +1 -1
  23. package/dist/lib/omnibar/omnibar-palette.component.js +126 -65
  24. package/dist/lib/omnibar/omnibar-palette.component.js.map +1 -1
  25. package/dist/lib/omnibar/omnibar-provider.d.ts +4 -0
  26. package/dist/lib/omnibar/omnibar-provider.d.ts.map +1 -1
  27. package/dist/lib/omnibar/omnibar-provider.js.map +1 -1
  28. package/dist/lib/omnibar/providers/omnibar-record.provider.d.ts +2 -2
  29. package/dist/lib/omnibar/providers/omnibar-record.provider.d.ts.map +1 -1
  30. package/dist/lib/omnibar/providers/omnibar-record.provider.js +3 -7
  31. package/dist/lib/omnibar/providers/omnibar-record.provider.js.map +1 -1
  32. package/dist/lib/omnibar/providers/omnibar-search.provider.d.ts +15 -2
  33. package/dist/lib/omnibar/providers/omnibar-search.provider.d.ts.map +1 -1
  34. package/dist/lib/omnibar/providers/omnibar-search.provider.js +59 -7
  35. package/dist/lib/omnibar/providers/omnibar-search.provider.js.map +1 -1
  36. package/dist/lib/profile/forking-setting.d.ts +8 -0
  37. package/dist/lib/profile/forking-setting.d.ts.map +1 -0
  38. package/dist/lib/profile/forking-setting.js +18 -0
  39. package/dist/lib/profile/forking-setting.js.map +1 -0
  40. package/dist/lib/profile/profile-dialog.component.d.ts +8 -0
  41. package/dist/lib/profile/profile-dialog.component.d.ts.map +1 -1
  42. package/dist/lib/profile/profile-dialog.component.js +182 -92
  43. package/dist/lib/profile/profile-dialog.component.js.map +1 -1
  44. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +16 -1
  45. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
  46. package/dist/lib/resource-wrappers/artifact-resource.component.js +27 -4
  47. package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
  48. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +24 -21
  49. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
  50. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +231 -54
  51. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
  52. package/dist/lib/resource-wrappers/chat-search-routing.d.ts +10 -3
  53. package/dist/lib/resource-wrappers/chat-search-routing.d.ts.map +1 -1
  54. package/dist/lib/resource-wrappers/chat-search-routing.js +11 -4
  55. package/dist/lib/resource-wrappers/chat-search-routing.js.map +1 -1
  56. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +12 -3
  57. package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
  58. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +18 -3
  59. package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
  60. package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.d.ts +29 -0
  61. package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.d.ts.map +1 -0
  62. package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.js +141 -0
  63. package/dist/lib/resource-wrappers/dashboard-add-to-menu.component.js.map +1 -0
  64. package/dist/lib/resource-wrappers/dashboard-part-config.d.ts +28 -0
  65. package/dist/lib/resource-wrappers/dashboard-part-config.d.ts.map +1 -0
  66. package/dist/lib/resource-wrappers/dashboard-part-config.js +150 -0
  67. package/dist/lib/resource-wrappers/dashboard-part-config.js.map +1 -0
  68. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +361 -56
  69. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
  70. package/dist/lib/resource-wrappers/dashboard-resource.component.js +1312 -361
  71. package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
  72. package/dist/lib/resource-wrappers/dashboard-source-search.d.ts +86 -0
  73. package/dist/lib/resource-wrappers/dashboard-source-search.d.ts.map +1 -0
  74. package/dist/lib/resource-wrappers/dashboard-source-search.js +136 -0
  75. package/dist/lib/resource-wrappers/dashboard-source-search.js.map +1 -0
  76. package/dist/lib/resource-wrappers/dashboard-studio-pane.d.ts +30 -0
  77. package/dist/lib/resource-wrappers/dashboard-studio-pane.d.ts.map +1 -0
  78. package/dist/lib/resource-wrappers/dashboard-studio-pane.js +55 -0
  79. package/dist/lib/resource-wrappers/dashboard-studio-pane.js.map +1 -0
  80. package/dist/lib/resource-wrappers/dashboard-studio-tools.d.ts +135 -0
  81. package/dist/lib/resource-wrappers/dashboard-studio-tools.d.ts.map +1 -0
  82. package/dist/lib/resource-wrappers/dashboard-studio-tools.js +856 -0
  83. package/dist/lib/resource-wrappers/dashboard-studio-tools.js.map +1 -0
  84. package/dist/lib/resource-wrappers/dashboard-tab-agent.d.ts +87 -0
  85. package/dist/lib/resource-wrappers/dashboard-tab-agent.d.ts.map +1 -0
  86. package/dist/lib/resource-wrappers/dashboard-tab-agent.js +125 -0
  87. package/dist/lib/resource-wrappers/dashboard-tab-agent.js.map +1 -0
  88. package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts +28 -7
  89. package/dist/lib/resource-wrappers/livekit-room-resource.component.d.ts.map +1 -1
  90. package/dist/lib/resource-wrappers/livekit-room-resource.component.js +174 -112
  91. package/dist/lib/resource-wrappers/livekit-room-resource.component.js.map +1 -1
  92. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts +3 -3
  93. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
  94. package/dist/lib/resource-wrappers/search-results-resource.component.js +10 -14
  95. package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
  96. package/dist/lib/server-connectivity/server-connectivity-banner.component.d.ts +16 -3
  97. package/dist/lib/server-connectivity/server-connectivity-banner.component.d.ts.map +1 -1
  98. package/dist/lib/server-connectivity/server-connectivity-banner.component.js +17 -3
  99. package/dist/lib/server-connectivity/server-connectivity-banner.component.js.map +1 -1
  100. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +41 -0
  101. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  102. package/dist/lib/shell/components/tabs/tab-container.component.js +170 -39
  103. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  104. package/dist/lib/shell/dashboard-tab-for-url.d.ts +35 -0
  105. package/dist/lib/shell/dashboard-tab-for-url.d.ts.map +1 -0
  106. package/dist/lib/shell/dashboard-tab-for-url.js +39 -0
  107. package/dist/lib/shell/dashboard-tab-for-url.js.map +1 -0
  108. package/dist/lib/shell/deep-link-workspace.d.ts +25 -0
  109. package/dist/lib/shell/deep-link-workspace.d.ts.map +1 -0
  110. package/dist/lib/shell/deep-link-workspace.js +49 -0
  111. package/dist/lib/shell/deep-link-workspace.js.map +1 -0
  112. package/dist/lib/shell/shell.component.d.ts +43 -2
  113. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  114. package/dist/lib/shell/shell.component.js +174 -42
  115. package/dist/lib/shell/shell.component.js.map +1 -1
  116. package/dist/lib/shell/shell.module.d.ts +2 -1
  117. package/dist/lib/shell/shell.module.d.ts.map +1 -1
  118. package/dist/lib/shell/shell.module.js +6 -2
  119. package/dist/lib/shell/shell.module.js.map +1 -1
  120. package/dist/lib/single-dashboard/single-dashboard.component.js +1 -1
  121. package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
  122. package/dist/lib/single-record/single-record.component.d.ts +12 -0
  123. package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
  124. package/dist/lib/single-record/single-record.component.js +49 -8
  125. package/dist/lib/single-record/single-record.component.js.map +1 -1
  126. package/dist/lib/template-user/template-user-banner.component.d.ts +20 -0
  127. package/dist/lib/template-user/template-user-banner.component.d.ts.map +1 -0
  128. package/dist/lib/template-user/template-user-banner.component.js +67 -0
  129. package/dist/lib/template-user/template-user-banner.component.js.map +1 -0
  130. package/dist/module.d.ts +61 -59
  131. package/dist/module.d.ts.map +1 -1
  132. package/dist/module.js +7 -0
  133. package/dist/module.js.map +1 -1
  134. package/dist/public-api.d.ts +1 -0
  135. package/dist/public-api.d.ts.map +1 -1
  136. package/dist/public-api.js +1 -0
  137. package/dist/public-api.js.map +1 -1
  138. package/package.json +58 -53
@@ -1,7 +1,7 @@
1
1
  import { Component, ViewChild, runInInjectionContext, createComponent, ViewEncapsulation, HostListener, Output, EventEmitter, inject } from '@angular/core';
2
- import { GoldenLayoutManager, FlattenLayoutToSingleStack } from '@memberjunction/ng-base-application';
2
+ import { GoldenLayoutManager, FlattenLayoutToSingleStack, TabService } from '@memberjunction/ng-base-application';
3
3
  import { MJGlobal } from '@memberjunction/global';
4
- import { BaseResourceComponent, HomeAppPinService, NavigationService, IsRecordTabsStyle, IsRecordsTabConfiguration, IsRecordsRegionTab, IsRecordDockedToWorkspace, RECORD_DOCKED_TO_WORKSPACE_KEY, GetRecordSourceContext, SafeDetectChanges, ExplorerBreakpointService, ResolveRecordTypeIcon } from '@memberjunction/ng-shared';
4
+ import { BaseResourceComponent, HomeAppPinService, NavigationService, IsRecordTabsStyle, IsRecordsTabConfiguration, IsRecordsRegionTab, IsRecordDockedToWorkspace, RECORD_DOCKED_TO_WORKSPACE_KEY, GetRecordSourceContext, SafeDetectChanges, ExplorerBreakpointService, ResolveRecordTypeIcon, GetDeletedRecordFromEvent, RecordTabShowsRecord, RemoveDeletedRecordFromOrigin, OriginToTabConfiguration, OriginHasReturnTarget, NormalizeRecordIdForMatch } from '@memberjunction/ng-shared';
5
5
  import { ResourceData, ResourcePermissionEngine } from '@memberjunction/core-entities';
6
6
  import { RecordOriginCrumbComponent } from '../record-open/record-origin-crumb.component';
7
7
  import { RecordSwitcherService } from '../record-open/record-switcher.service';
@@ -141,6 +141,8 @@ function TabContainerComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
141
141
  } }
142
142
  /** Fallback tab accent when an app has no color (matches pre-existing usage) */
143
143
  const DEFAULT_APP_COLOR = '#757575';
144
+ /** A single-field key in URL-segment form ('Field|value'); group 1 is the value. */
145
+ const SINGLE_FIELD_URL_SEGMENT = /^[^|]+\|([^|]+)$/;
144
146
  /**
145
147
  * Container for Golden Layout tabs with app-colored styling.
146
148
  *
@@ -214,6 +216,7 @@ export class TabContainerComponent extends BaseAngularComponent {
214
216
  layoutInitError = this.LayoutInitError;
215
217
  pinService = inject(HomeAppPinService);
216
218
  navigationService = inject(NavigationService);
219
+ tabService = inject(TabService);
217
220
  recordSwitcher = inject(RecordSwitcherService);
218
221
  subscriptions = [];
219
222
  layoutInitRetryCount = 0;
@@ -471,6 +474,7 @@ export class TabContainerComponent extends BaseAngularComponent {
471
474
  flushRegionCd() {
472
475
  SafeDetectChanges(this.cdr);
473
476
  }
477
+ recordsLayoutInitRetryCount = 0;
474
478
  /**
475
479
  * Keep the RECORDS region in lockstep with the workspace configuration:
476
480
  * resolve region visibility (active tab is a record), lazily initialize the
@@ -491,6 +495,13 @@ export class TabContainerComponent extends BaseAngularComponent {
491
495
  const recordTabs = config.tabs.filter(t => this.isRecordTab(t));
492
496
  const activeTab = config.tabs.find(t => t.id === config.activeTabId);
493
497
  const showing = !!activeTab && this.isRecordTab(activeTab);
498
+ if (showing !== this.ShowRecordsRegion) {
499
+ this.ShowRecordsRegion = showing;
500
+ // Flush NOW — the visibility class must land BEFORE records GL init/tab
501
+ // creation so synchronous TabShown events see ShowRecordsRegion=true and
502
+ // load the active record immediately instead of parking it.
503
+ this.flushRegionCd();
504
+ }
494
505
  // EAGER init + sync on EVERY emission. The region container always keeps
495
506
  // its full layout box (visibility-hidden, never display:none — see the
496
507
  // component CSS), so the records GL can initialize and reconcile even
@@ -504,14 +515,8 @@ export class TabContainerComponent extends BaseAngularComponent {
504
515
  if (this.recordsLayoutInitialized) {
505
516
  this.syncRecordsTabs(recordTabs);
506
517
  }
507
- if (showing !== this.ShowRecordsRegion) {
508
- this.ShowRecordsRegion = showing;
509
- // Flush NOW — the visibility class must land in this pass, not
510
- // whenever the next unrelated emission happens to run CD.
511
- this.flushRegionCd();
512
- if (showing) {
513
- this.flushPendingRecordShows();
514
- }
518
+ if (this.ShowRecordsRegion) {
519
+ this.flushPendingRecordShows();
515
520
  }
516
521
  if (showing && activeTab && this.recordsLayoutInitialized) {
517
522
  this.focusRecordsTab(activeTab.id);
@@ -734,17 +739,24 @@ export class TabContainerComponent extends BaseAngularComponent {
734
739
  return;
735
740
  }
736
741
  const container = this.RecordsGlContainer?.nativeElement;
737
- if (!container) {
738
- return; // Template not settled yet — the next sync pass retries
739
- }
740
- // NEVER initialize GL inside a zero-size (hidden) container — it bakes a
741
- // 0x0 internal size into every stack it later creates and the region
742
- // renders collapsed forever. Happens when the region flip is reverted
743
- // (activation stolen) before this deferred init runs; staying
744
- // uninitialized is safe — the next real show retries.
745
- if (container.getBoundingClientRect().height === 0) {
742
+ if (!container || container.getBoundingClientRect().height === 0) {
743
+ // NEVER initialize GL inside a missing or zero-size (hidden) container —
744
+ // it bakes a 0x0 internal size into every stack it later creates and the
745
+ // region renders collapsed forever. If the records region IS the active
746
+ // surface (e.g. startup deep link to a record before layout settles),
747
+ // schedule a bounded retry so initialization is not stranded.
748
+ if (this.ShowRecordsRegion && this.recordsLayoutInitRetryCount < this.MAX_LAYOUT_INIT_RETRIES) {
749
+ this.recordsLayoutInitRetryCount++;
750
+ setTimeout(() => {
751
+ const latest = this.workspaceManager.GetConfiguration();
752
+ if (latest && !this.recordsLayoutInitialized) {
753
+ this.syncRecordsRegion(latest);
754
+ }
755
+ }, 50);
756
+ }
746
757
  return;
747
758
  }
759
+ this.recordsLayoutInitRetryCount = 0;
748
760
  // Mobile: headerless GL — the record bar replaces the strip
749
761
  this.recordsLayoutManager.Initialize(container, { HideHeaders: this.mobileRecordsActive });
750
762
  this.recordsLayoutInitialized = true;
@@ -763,6 +775,14 @@ export class TabContainerComponent extends BaseAngularComponent {
763
775
  // loading such a layout would render a ghost pane for a closed record.
764
776
  if (layoutToLoad?.root && recordTabs.length > 0 && this.layoutCoversExactTabSet(layoutToLoad.root, recordTabs)) {
765
777
  if (this.recordsLayoutManager.LoadLayout(layoutToLoad)) {
778
+ recordTabs.forEach(tab => this.updateTabDisplayName(tab));
779
+ setTimeout(() => {
780
+ const latest = this.workspaceManager.GetConfiguration();
781
+ if (latest?.activeTabId && this.ShowRecordsRegion) {
782
+ this.focusRecordsTab(latest.activeTabId);
783
+ }
784
+ this.recordsLayoutManager.updateSize();
785
+ }, 50);
766
786
  return;
767
787
  }
768
788
  }
@@ -872,6 +892,23 @@ export class TabContainerComponent extends BaseAngularComponent {
872
892
  this.ShowContextMenu(event.x, event.y, event.tabId, event.anchorEl);
873
893
  }));
874
894
  this.wireRecordsLayoutEvents();
895
+ // A record deleted in this session: close every tab showing it and move the
896
+ // user off it (see HandleRecordDeleted). Remote deletes are left to the
897
+ // record tab itself, which shows a banner rather than vanishing under the user.
898
+ this.subscriptions.push(MJGlobal.Instance.GetEventListener().subscribe(event => {
899
+ // Read the key NOW: right after raising 'delete' the entity calls
900
+ // NewRecord() and its key is gone.
901
+ const deleted = GetDeletedRecordFromEvent(event);
902
+ if (!deleted || deleted.Remote) {
903
+ return;
904
+ }
905
+ // Deferred one macrotask: the event fires inside BaseEntity.Delete(), and
906
+ // the deleting form still has to finish (success toast, RecordDeleted
907
+ // handlers) before its tab is torn down.
908
+ setTimeout(() => {
909
+ this.HandleRecordDeleted(deleted).catch(err => LogError(err));
910
+ }, 0);
911
+ }));
875
912
  // Breakpoint crossings rebuild the records GL in the other chrome mode
876
913
  // (headerless+flattened vs full strip+splits). Skip-if-equal: the
877
914
  // BehaviorSubject replays the current value on subscribe.
@@ -1034,6 +1071,11 @@ export class TabContainerComponent extends BaseAngularComponent {
1034
1071
  }));
1035
1072
  }
1036
1073
  ngAfterViewInit() {
1074
+ const config = this.workspaceManager.GetConfiguration();
1075
+ if (config) {
1076
+ this.syncRecordsRegion(config);
1077
+ this.updateRecordSurfaceState(config);
1078
+ }
1037
1079
  // Initialize Golden Layout only if we're not in single-resource mode
1038
1080
  if (!this.UseSingleResourceMode) {
1039
1081
  this.initializeGoldenLayout();
@@ -1725,13 +1767,26 @@ export class TabContainerComponent extends BaseAngularComponent {
1725
1767
  const config = this.workspaceManager.GetConfiguration();
1726
1768
  const tab = config?.tabs.find(t => t.id === tabId);
1727
1769
  const isLastTab = config?.tabs.length === 1 && config.tabs[0].id === tabId;
1728
- // DESTROY (not just detach) the cached component before closing. The default
1729
- // tab-close path detaches and keeps components alive for reuse — but the
1730
- // discarded form is exactly what we DON'T want to keep: it holds a stale
1731
- // BaseEntity in view mode. The next "Create New Record" click for the same
1732
- // entity would otherwise hit the cache discriminator lookup, find this stale
1733
- // component, and reattach it — surfacing a blank form in view mode instead
1734
- // of a fresh edit-mode form. Destroy here forces a cache miss on the next click.
1770
+ this.closeTabDestroyingComponent(tabId);
1771
+ if (isLastTab && tab) {
1772
+ // CloseTab kept the tab around but unpinned it. Replace it with the app's
1773
+ // default tab so the user lands on a meaningful surface (typically the
1774
+ // home dashboard) instead of staying on the discarded form.
1775
+ await this.openAppDefaultTab(tab.applicationId);
1776
+ }
1777
+ }
1778
+ /**
1779
+ * Close a tab and DESTROY (not just detach) its cached component.
1780
+ *
1781
+ * The default tab-close path detaches and keeps components alive for reuse —
1782
+ * but a discarded or deleted record's form is exactly what we DON'T want to
1783
+ * keep: it holds a stale BaseEntity in view mode. The next "Create New Record"
1784
+ * click for the same entity would otherwise hit the cache discriminator
1785
+ * lookup, find this stale component, and reattach it — surfacing a blank form
1786
+ * in view mode instead of a fresh edit-mode form. Destroy here forces a cache
1787
+ * miss on the next open.
1788
+ */
1789
+ closeTabDestroyingComponent(tabId) {
1735
1790
  if (this.singleResourceCacheIdentity?.tabId === tabId) {
1736
1791
  // Single-resource mode: the component is tracked via singleResource* fields,
1737
1792
  // and its cache entry is currently marked attached. Use the identity directly
@@ -1742,7 +1797,7 @@ export class TabContainerComponent extends BaseAngularComponent {
1742
1797
  this.singleResourceComponentRef = null;
1743
1798
  this.singleResourceCacheIdentity = null;
1744
1799
  // Clear the host container's DOM so the user isn't briefly looking at the
1745
- // destroyed component's wrapper between CloseTab and the default-tab load.
1800
+ // destroyed component's wrapper between CloseTab and the next tab's load.
1746
1801
  const directContainer = this.DirectContentContainer?.nativeElement;
1747
1802
  if (directContainer) {
1748
1803
  while (directContainer.firstChild)
@@ -1756,18 +1811,78 @@ export class TabContainerComponent extends BaseAngularComponent {
1756
1811
  this.componentRefs.delete(tabId);
1757
1812
  }
1758
1813
  this.workspaceManager.CloseTab(tabId);
1759
- if (isLastTab && tab) {
1760
- // CloseTab kept the tab around but unpinned it. Replace it with the app's
1761
- // default tab so the user lands on a meaningful surface (typically the
1762
- // home dashboard) instead of staying on the discarded form.
1763
- const app = this.appManager.GetAppById(tab.applicationId);
1764
- if (app) {
1765
- const defaultTabRequest = await app.CreateDefaultTab();
1766
- if (defaultTabRequest) {
1767
- this.workspaceManager.OpenTab(defaultTabRequest, app.GetColor());
1768
- }
1814
+ }
1815
+ /** Open an app's default tab (its home / landing surface), if it has one. */
1816
+ async openAppDefaultTab(appId) {
1817
+ const app = this.appManager.GetAppById(appId);
1818
+ if (app) {
1819
+ const defaultTabRequest = await app.CreateDefaultTab();
1820
+ if (defaultTabRequest) {
1821
+ this.workspaceManager.OpenTab(defaultTabRequest, app.GetColor());
1822
+ }
1823
+ }
1824
+ }
1825
+ /**
1826
+ * A record was deleted in this session — from its own form, a custom form, a
1827
+ * grid row, or any other `BaseEntity.Delete()`. Nothing may keep showing it:
1828
+ *
1829
+ * 1. Other record tabs whose origin chain names it get the record spliced out,
1830
+ * so their crumb returns to where the user was before it.
1831
+ * 2. Every record tab showing it closes, its cached form destroyed.
1832
+ * 3. If one of those was the ACTIVE tab, the user is taken back to where it
1833
+ * was opened from; with no origin (e.g. loaded straight from a URL), to the
1834
+ * Home dashboard, or the tab's own app landing page when there is no Home
1835
+ * app. Its URL's history entry is replaced so browser Back can't reopen it.
1836
+ *
1837
+ * Inactive tabs showing it just close: the user wasn't looking at them.
1838
+ */
1839
+ async HandleRecordDeleted(record) {
1840
+ const config = this.workspaceManager.GetConfiguration();
1841
+ if (!config) {
1842
+ return;
1843
+ }
1844
+ const showing = config.tabs.filter(t => RecordTabShowsRecord(t, record));
1845
+ const showingIds = new Set(showing.map(t => t.id));
1846
+ for (const tab of config.tabs) {
1847
+ if (showingIds.has(tab.id) || !IsRecordsTabConfiguration(tab.configuration)) {
1848
+ continue;
1849
+ }
1850
+ const origin = GetRecordSourceContext(tab.configuration) ?? undefined;
1851
+ const pruned = RemoveDeletedRecordFromOrigin(origin, record);
1852
+ if (pruned !== origin) {
1853
+ this.workspaceManager.UpdateTabConfiguration(tab.id, OriginToTabConfiguration(pruned));
1769
1854
  }
1770
1855
  }
1856
+ if (showing.length === 0) {
1857
+ return;
1858
+ }
1859
+ const activeTab = showing.find(t => t.id === config.activeTabId);
1860
+ const returnTo = activeTab
1861
+ ? RemoveDeletedRecordFromOrigin(GetRecordSourceContext(activeTab.configuration) ?? undefined, record)
1862
+ : undefined;
1863
+ if (activeTab) {
1864
+ // Mark by what the tab's URL carries (its own entity string and id), not
1865
+ // the event's canonical name: the URL is built from the tab.
1866
+ const urlEntity = activeTab.configuration?.['Entity'];
1867
+ this.tabService.MarkRecordUrlForReplacement(typeof urlEntity === 'string' ? urlEntity : record.EntityName, NormalizeRecordIdForMatch(activeTab.resourceRecordId || record.RecordId));
1868
+ }
1869
+ for (const tab of showing) {
1870
+ this.closeTabDestroyingComponent(tab.id);
1871
+ }
1872
+ if (activeTab) {
1873
+ await this.leaveDeletedRecord(activeTab, returnTo);
1874
+ }
1875
+ }
1876
+ /** Where the user goes after the record they were looking at is deleted. */
1877
+ async leaveDeletedRecord(deletedTab, origin) {
1878
+ if (OriginHasReturnTarget(origin)) {
1879
+ await this.navigationService.ReturnToRecordSource(origin);
1880
+ return;
1881
+ }
1882
+ if (await this.navigationService.OpenHomeDashboard()) {
1883
+ return;
1884
+ }
1885
+ await this.openAppDefaultTab(deletedTab.applicationId);
1771
1886
  }
1772
1887
  /**
1773
1888
  * Generate a signature for tab content to detect when content changes
@@ -2176,6 +2291,18 @@ export class TabContainerComponent extends BaseAngularComponent {
2176
2291
  const normalize = (name) => (name ?? '').trim().toLowerCase().replace(/^mj:\s*/, '');
2177
2292
  return normalize(a) === normalize(b);
2178
2293
  }
2294
+ /**
2295
+ * Whether two resource record ids name the same record. A component's ResourceRecordSaved
2296
+ * stores a single-field key in the URL-segment form ('ID|<uuid>'), while its tab may keep the
2297
+ * bare value ('<uuid>'). The field name is dropped only when the other id is a bare value, so
2298
+ * two ids that both contain '|' are compared as they are.
2299
+ */
2300
+ static IsSameRecordId(a, b) {
2301
+ const left = (a ?? '').trim();
2302
+ const right = (b ?? '').trim();
2303
+ const bareValue = (id) => SINGLE_FIELD_URL_SEGMENT.exec(id)?.[1] ?? id;
2304
+ return left === right || bareValue(left) === right || left === bareValue(right);
2305
+ }
2179
2306
  async getResourceTypeId(resourceType) {
2180
2307
  const rt = await this.getResourceTypeEntity(resourceType);
2181
2308
  if (rt) {
@@ -2386,9 +2513,13 @@ export class TabContainerComponent extends BaseAngularComponent {
2386
2513
  // reload from being handed back the component it just detached.
2387
2514
  const existingEntity = existingResourceData?.Configuration?.Entity;
2388
2515
  const newEntity = tab.configuration['Entity'];
2516
+ // The record id comparison accepts the URL-segment form ResourceRecordSaved writes into
2517
+ // the component's Data ('ID|<uuid>' for a tab that keeps '<uuid>'). An exact comparison
2518
+ // reloads the same cached component on every configuration emission, and each reload
2519
+ // emits a new configuration.
2389
2520
  const needsReload = !TabContainerComponent.IsSameResourceType(existingResourceData?.ResourceType, tab.configuration['resourceType']) ||
2390
2521
  existingResourceData?.Configuration?.applicationId !== tab.applicationId ||
2391
- existingRecordId !== newRecordId ||
2522
+ !TabContainerComponent.IsSameRecordId(existingRecordId, newRecordId) ||
2392
2523
  existingEntity !== newEntity ||
2393
2524
  (tab.configuration['resourceType'] === 'Custom' && existingDriverClass !== newDriverClass);
2394
2525
  if (needsReload) {
@@ -2953,5 +3084,5 @@ export class TabContainerComponent extends BaseAngularComponent {
2953
3084
  type: HostListener,
2954
3085
  args: ['window:resize']
2955
3086
  }] }); })();
2956
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TabContainerComponent, { className: "TabContainerComponent", filePath: "src/lib/shell/components/tabs/tab-container.component.ts", lineNumber: 62 }); })();
3087
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TabContainerComponent, { className: "TabContainerComponent", filePath: "src/lib/shell/components/tabs/tab-container.component.ts", lineNumber: 68 }); })();
2957
3088
  //# sourceMappingURL=tab-container.component.js.map