@memberjunction/ng-shared 5.23.0 → 5.25.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=navigation-framework.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-framework.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/navigation-framework.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Tests for the Navigation Back/Forward Framework:
3
+ * - BaseResourceComponent query param lifecycle (OnQueryParamsChanged, UpdateQueryParams, GetQueryParams)
4
+ * - NavigationService.NotifyQueryParamsChanged / QueryParamChanged$
5
+ * - Suppression flag prevents loops
6
+ * - Tab-scoped filtering prevents cross-tab leakage
7
+ */
8
+ import { describe, it, expect, vi } from 'vitest';
9
+ // Mock Angular dependencies
10
+ vi.mock('@angular/core', () => ({
11
+ Directive: () => (target) => target,
12
+ Injectable: () => (target) => target,
13
+ OnInit: class {
14
+ },
15
+ OnDestroy: class {
16
+ },
17
+ inject: vi.fn(),
18
+ Input: () => () => { },
19
+ Output: () => () => { },
20
+ EventEmitter: class {
21
+ emit() { }
22
+ },
23
+ }));
24
+ vi.mock('@angular/router', () => ({}));
25
+ vi.mock('@memberjunction/core', () => ({
26
+ BaseEntity: class {
27
+ },
28
+ Metadata: class {
29
+ },
30
+ CompositeKey: class {
31
+ },
32
+ }));
33
+ vi.mock('@memberjunction/core-entities', () => ({
34
+ ResourceData: class {
35
+ ID = 0;
36
+ Name = '';
37
+ ResourceTypeID = '';
38
+ ResourceRecordID = '';
39
+ Configuration = {};
40
+ constructor(data) {
41
+ if (data)
42
+ Object.assign(this, data);
43
+ }
44
+ },
45
+ }));
46
+ vi.mock('@memberjunction/global', () => ({
47
+ UUIDsEqual: (a, b) => a === b,
48
+ }));
49
+ // ---- QueryParamChangeEvent tests ----
50
+ describe('QueryParamChangeEvent', () => {
51
+ it('should have TabId and Params fields', () => {
52
+ // Verify the interface shape by creating a conforming object
53
+ const event = {
54
+ TabId: 'tab-123',
55
+ Params: { entity: 'Actions', filter: 'active' },
56
+ };
57
+ expect(event.TabId).toBe('tab-123');
58
+ expect(event.Params.entity).toBe('Actions');
59
+ expect(event.Params.filter).toBe('active');
60
+ });
61
+ });
62
+ // ---- Shell helper function tests (pure functions extracted for testing) ----
63
+ describe('extractQueryParamsFromUrl', () => {
64
+ // Replicate the shell's extractQueryParamsFromUrl logic for unit testing
65
+ function extractQueryParamsFromUrl(url) {
66
+ const fragmentIndex = url.indexOf('#');
67
+ const cleanUrl = fragmentIndex !== -1 ? url.substring(0, fragmentIndex) : url;
68
+ const queryIndex = cleanUrl.indexOf('?');
69
+ if (queryIndex === -1)
70
+ return {};
71
+ const params = new URLSearchParams(cleanUrl.substring(queryIndex + 1));
72
+ const result = {};
73
+ params.forEach((value, key) => { result[key] = value; });
74
+ return result;
75
+ }
76
+ it('should extract query params from URL', () => {
77
+ const result = extractQueryParamsFromUrl('/app/data-explorer/Data?entity=Actions&filter=active');
78
+ expect(result).toEqual({ entity: 'Actions', filter: 'active' });
79
+ });
80
+ it('should return empty object for URL without query params', () => {
81
+ expect(extractQueryParamsFromUrl('/app/data-explorer/Data')).toEqual({});
82
+ });
83
+ it('should decode encoded values', () => {
84
+ const result = extractQueryParamsFromUrl('/app/data?entity=MJ%3A%20Actions');
85
+ expect(result.entity).toBe('MJ: Actions');
86
+ });
87
+ it('should handle empty values', () => {
88
+ const result = extractQueryParamsFromUrl('/app/data?key=');
89
+ expect(result.key).toBe('');
90
+ });
91
+ it('should ignore fragment (#hash)', () => {
92
+ const result = extractQueryParamsFromUrl('/app/data?entity=Members#section');
93
+ expect(result.entity).toBe('Members');
94
+ expect(result['#section']).toBeUndefined();
95
+ });
96
+ it('should handle URL with only fragment, no query params', () => {
97
+ const result = extractQueryParamsFromUrl('/app/data#section');
98
+ expect(result).toEqual({});
99
+ });
100
+ it('should handle + as space', () => {
101
+ const result = extractQueryParamsFromUrl('/app/data?entity=My+Entity');
102
+ expect(result.entity).toBe('My Entity');
103
+ });
104
+ });
105
+ describe('queryParamsEqual', () => {
106
+ // Replicate the shell's queryParamsEqual logic
107
+ function queryParamsEqual(a, b) {
108
+ const keysA = Object.keys(a);
109
+ const keysB = Object.keys(b);
110
+ if (keysA.length !== keysB.length)
111
+ return false;
112
+ return keysA.every(key => decodeURIComponent(a[key]?.replace(/\+/g, ' ') || '') ===
113
+ decodeURIComponent(b[key]?.replace(/\+/g, ' ') || ''));
114
+ }
115
+ it('should return true for identical params', () => {
116
+ expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Actions' })).toBe(true);
117
+ });
118
+ it('should return false for different values', () => {
119
+ expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Members' })).toBe(false);
120
+ });
121
+ it('should return false for different key counts', () => {
122
+ expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Actions', filter: 'x' })).toBe(false);
123
+ });
124
+ it('should return true for both empty', () => {
125
+ expect(queryParamsEqual({}, {})).toBe(true);
126
+ });
127
+ it('should return false for one empty, one not', () => {
128
+ expect(queryParamsEqual({}, { entity: 'Actions' })).toBe(false);
129
+ });
130
+ it('should normalize + vs %20 encoding', () => {
131
+ expect(queryParamsEqual({ entity: 'My+Entity' }, { entity: 'My%20Entity' })).toBe(true);
132
+ });
133
+ it('should handle missing keys', () => {
134
+ expect(queryParamsEqual({ a: 'x' }, { b: 'x' })).toBe(false);
135
+ });
136
+ });
137
+ // ---- buildResourceUrl appendQP helper tests ----
138
+ describe('appendQueryParams (appendQP helper)', () => {
139
+ function appendQP(url, queryParams) {
140
+ if (!queryParams || Object.keys(queryParams).length === 0)
141
+ return url;
142
+ const separator = url.includes('?') ? '&' : '?';
143
+ const params = new URLSearchParams(queryParams);
144
+ return `${url}${separator}${params.toString()}`;
145
+ }
146
+ it('should append params to URL without existing params', () => {
147
+ const result = appendQP('/app/data/record/Entity/123', { entity: 'Actions' });
148
+ expect(result).toBe('/app/data/record/Entity/123?entity=Actions');
149
+ });
150
+ it('should append params to URL with existing params', () => {
151
+ const result = appendQP('/app/data?existing=x', { entity: 'Actions' });
152
+ expect(result).toBe('/app/data?existing=x&entity=Actions');
153
+ });
154
+ it('should return URL unchanged for empty params', () => {
155
+ expect(appendQP('/app/data', {})).toBe('/app/data');
156
+ });
157
+ it('should return URL unchanged for undefined params', () => {
158
+ expect(appendQP('/app/data', undefined)).toBe('/app/data');
159
+ });
160
+ it('should properly encode special characters', () => {
161
+ const result = appendQP('/app/data', { entity: 'MJ: Actions' });
162
+ expect(result).toContain('entity=MJ');
163
+ // URLSearchParams encodes spaces as +
164
+ expect(result).toMatch(/entity=MJ[+%].*Actions/);
165
+ });
166
+ it('should handle multiple params', () => {
167
+ const result = appendQP('/app/data', { entity: 'Actions', filter: 'active', view: 'grid' });
168
+ expect(result).toContain('entity=Actions');
169
+ expect(result).toContain('filter=active');
170
+ expect(result).toContain('view=grid');
171
+ });
172
+ });
173
+ // ---- shouldReuseRoute logic tests ----
174
+ describe('shouldReuseRoute logic', () => {
175
+ // Test the comparison logic (routeConfig + params, NOT queryParams)
176
+ function objectContentsEqual(obj1, obj2) {
177
+ if (obj1 === obj2)
178
+ return true;
179
+ if (!obj1 || !obj2)
180
+ return false;
181
+ const keys1 = Object.keys(obj1);
182
+ const keys2 = Object.keys(obj2);
183
+ if (keys1.length !== keys2.length)
184
+ return false;
185
+ return keys1.every(key => obj1[key] === obj2[key]);
186
+ }
187
+ function shouldReuseRoute(futureConfig, currConfig, futureParams, currParams) {
188
+ return futureConfig === currConfig && objectContentsEqual(futureParams, currParams);
189
+ }
190
+ it('should return true for same config and params with different query params', () => {
191
+ const config = {};
192
+ // Query params intentionally excluded
193
+ expect(shouldReuseRoute(config, config, { id: '1' }, { id: '1' })).toBe(true);
194
+ });
195
+ it('should return false for different path params', () => {
196
+ const config = {};
197
+ expect(shouldReuseRoute(config, config, { id: '1' }, { id: '2' })).toBe(false);
198
+ });
199
+ it('should return false for different route configs', () => {
200
+ expect(shouldReuseRoute({}, {}, { id: '1' }, { id: '1' })).toBe(false);
201
+ });
202
+ it('should return true for same everything', () => {
203
+ const config = {};
204
+ expect(shouldReuseRoute(config, config, {}, {})).toBe(true);
205
+ });
206
+ it('should return false when one has params and other does not', () => {
207
+ const config = {};
208
+ expect(shouldReuseRoute(config, config, { id: '1' }, {})).toBe(false);
209
+ });
210
+ });
211
+ // ---- Suppression flag behavior ----
212
+ describe('_suppressQueryParamSync behavior', () => {
213
+ it('should prevent UpdateQueryParams during suppression', () => {
214
+ let suppressFlag = false;
215
+ const mockUpdateActiveTabQP = vi.fn();
216
+ function updateQueryParams(params) {
217
+ if (suppressFlag)
218
+ return;
219
+ mockUpdateActiveTabQP(params);
220
+ }
221
+ // Normal call
222
+ updateQueryParams({ entity: 'Actions' });
223
+ expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(1);
224
+ // Suppressed call (simulates being inside OnQueryParamsChanged)
225
+ suppressFlag = true;
226
+ updateQueryParams({ entity: 'Members' });
227
+ expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(1); // Still 1, not 2
228
+ // After suppression cleared
229
+ suppressFlag = false;
230
+ updateQueryParams({ entity: 'Queries' });
231
+ expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(2);
232
+ });
233
+ it('should clear suppression flag even if OnQueryParamsChanged throws', () => {
234
+ let suppressFlag = false;
235
+ function simulateSubscription(callback) {
236
+ suppressFlag = true;
237
+ try {
238
+ callback();
239
+ }
240
+ finally {
241
+ suppressFlag = false;
242
+ }
243
+ }
244
+ // The try/finally ensures flag is cleared even when callback throws
245
+ try {
246
+ simulateSubscription(() => {
247
+ throw new Error('Component error');
248
+ });
249
+ }
250
+ catch {
251
+ // Expected — the error propagates but flag should still be cleared
252
+ }
253
+ // Flag should be cleared despite the error
254
+ expect(suppressFlag).toBe(false);
255
+ });
256
+ });
257
+ // ---- GetQueryParams behavior ----
258
+ describe('GetQueryParams', () => {
259
+ it('should return params from Configuration.queryParams', () => {
260
+ const config = { queryParams: { entity: 'Actions', filter: 'active' } };
261
+ const result = config['queryParams'] || {};
262
+ expect(result).toEqual({ entity: 'Actions', filter: 'active' });
263
+ });
264
+ it('should return empty object when no queryParams', () => {
265
+ const config = {};
266
+ const result = config['queryParams'] || {};
267
+ expect(result).toEqual({});
268
+ });
269
+ it('should return empty object when Configuration is null', () => {
270
+ const config = null;
271
+ const result = (config?.['queryParams'] ?? {});
272
+ expect(result).toEqual({});
273
+ });
274
+ });
275
+ // ---- Tab-scoped filtering ----
276
+ describe('Tab-scoped query param filtering', () => {
277
+ it('should only deliver events matching the component tab ID', () => {
278
+ const componentTabId = 'tab-abc';
279
+ const events = [
280
+ { TabId: 'tab-abc', Params: { entity: 'Actions' } },
281
+ { TabId: 'tab-xyz', Params: { entity: 'Members' } },
282
+ { TabId: 'tab-abc', Params: { entity: 'Queries' } },
283
+ ];
284
+ const received = events.filter(e => e.TabId === componentTabId);
285
+ expect(received).toHaveLength(2);
286
+ expect(received[0].Params.entity).toBe('Actions');
287
+ expect(received[1].Params.entity).toBe('Queries');
288
+ });
289
+ it('should not deliver any events for non-matching tab ID', () => {
290
+ const componentTabId = 'tab-none';
291
+ const events = [
292
+ { TabId: 'tab-abc', Params: { entity: 'Actions' } },
293
+ { TabId: 'tab-xyz', Params: { entity: 'Members' } },
294
+ ];
295
+ const received = events.filter(e => e.TabId === componentTabId);
296
+ expect(received).toHaveLength(0);
297
+ });
298
+ it('should handle empty tab ID gracefully', () => {
299
+ const componentTabId = '';
300
+ const events = [
301
+ { TabId: 'tab-abc', Params: { entity: 'Actions' } },
302
+ ];
303
+ const received = events.filter(e => e.TabId === componentTabId);
304
+ expect(received).toHaveLength(0);
305
+ });
306
+ });
307
+ //# sourceMappingURL=navigation-framework.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-framework.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/navigation-framework.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tests for the Navigation Back/Forward Framework:\n * - BaseResourceComponent query param lifecycle (OnQueryParamsChanged, UpdateQueryParams, GetQueryParams)\n * - NavigationService.NotifyQueryParamsChanged / QueryParamChanged$\n * - Suppression flag prevents loops\n * - Tab-scoped filtering prevents cross-tab leakage\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// Mock Angular dependencies\nvi.mock('@angular/core', () => ({\n Directive: () => (target: Function) => target,\n Injectable: () => (target: Function) => target,\n OnInit: class {},\n OnDestroy: class {},\n inject: vi.fn(),\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n}));\n\nvi.mock('@angular/router', () => ({}));\n\nvi.mock('@memberjunction/core', () => ({\n BaseEntity: class {},\n Metadata: class {},\n CompositeKey: class {},\n}));\n\nvi.mock('@memberjunction/core-entities', () => ({\n ResourceData: class {\n ID = 0;\n Name = '';\n ResourceTypeID = '';\n ResourceRecordID = '';\n Configuration: Record<string, unknown> = {};\n constructor(data?: Record<string, unknown>) {\n if (data) Object.assign(this, data);\n }\n },\n}));\n\nvi.mock('@memberjunction/global', () => ({\n UUIDsEqual: (a: string, b: string) => a === b,\n}));\n\n// ---- QueryParamChangeEvent tests ----\n\ndescribe('QueryParamChangeEvent', () => {\n it('should have TabId and Params fields', () => {\n // Verify the interface shape by creating a conforming object\n const event = {\n TabId: 'tab-123',\n Params: { entity: 'Actions', filter: 'active' },\n };\n expect(event.TabId).toBe('tab-123');\n expect(event.Params.entity).toBe('Actions');\n expect(event.Params.filter).toBe('active');\n });\n});\n\n// ---- Shell helper function tests (pure functions extracted for testing) ----\n\ndescribe('extractQueryParamsFromUrl', () => {\n // Replicate the shell's extractQueryParamsFromUrl logic for unit testing\n function extractQueryParamsFromUrl(url: string): Record<string, string> {\n const fragmentIndex = url.indexOf('#');\n const cleanUrl = fragmentIndex !== -1 ? url.substring(0, fragmentIndex) : url;\n const queryIndex = cleanUrl.indexOf('?');\n if (queryIndex === -1) return {};\n const params = new URLSearchParams(cleanUrl.substring(queryIndex + 1));\n const result: Record<string, string> = {};\n params.forEach((value, key) => { result[key] = value; });\n return result;\n }\n\n it('should extract query params from URL', () => {\n const result = extractQueryParamsFromUrl('/app/data-explorer/Data?entity=Actions&filter=active');\n expect(result).toEqual({ entity: 'Actions', filter: 'active' });\n });\n\n it('should return empty object for URL without query params', () => {\n expect(extractQueryParamsFromUrl('/app/data-explorer/Data')).toEqual({});\n });\n\n it('should decode encoded values', () => {\n const result = extractQueryParamsFromUrl('/app/data?entity=MJ%3A%20Actions');\n expect(result.entity).toBe('MJ: Actions');\n });\n\n it('should handle empty values', () => {\n const result = extractQueryParamsFromUrl('/app/data?key=');\n expect(result.key).toBe('');\n });\n\n it('should ignore fragment (#hash)', () => {\n const result = extractQueryParamsFromUrl('/app/data?entity=Members#section');\n expect(result.entity).toBe('Members');\n expect(result['#section']).toBeUndefined();\n });\n\n it('should handle URL with only fragment, no query params', () => {\n const result = extractQueryParamsFromUrl('/app/data#section');\n expect(result).toEqual({});\n });\n\n it('should handle + as space', () => {\n const result = extractQueryParamsFromUrl('/app/data?entity=My+Entity');\n expect(result.entity).toBe('My Entity');\n });\n});\n\ndescribe('queryParamsEqual', () => {\n // Replicate the shell's queryParamsEqual logic\n function queryParamsEqual(a: Record<string, string>, b: Record<string, string>): boolean {\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n return keysA.every(key =>\n decodeURIComponent(a[key]?.replace(/\\+/g, ' ') || '') ===\n decodeURIComponent(b[key]?.replace(/\\+/g, ' ') || '')\n );\n }\n\n it('should return true for identical params', () => {\n expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Actions' })).toBe(true);\n });\n\n it('should return false for different values', () => {\n expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Members' })).toBe(false);\n });\n\n it('should return false for different key counts', () => {\n expect(queryParamsEqual({ entity: 'Actions' }, { entity: 'Actions', filter: 'x' })).toBe(false);\n });\n\n it('should return true for both empty', () => {\n expect(queryParamsEqual({}, {})).toBe(true);\n });\n\n it('should return false for one empty, one not', () => {\n expect(queryParamsEqual({}, { entity: 'Actions' })).toBe(false);\n });\n\n it('should normalize + vs %20 encoding', () => {\n expect(queryParamsEqual({ entity: 'My+Entity' }, { entity: 'My%20Entity' })).toBe(true);\n });\n\n it('should handle missing keys', () => {\n expect(queryParamsEqual({ a: 'x' }, { b: 'x' })).toBe(false);\n });\n});\n\n// ---- buildResourceUrl appendQP helper tests ----\n\ndescribe('appendQueryParams (appendQP helper)', () => {\n function appendQP(url: string, queryParams: Record<string, string> | undefined): string {\n if (!queryParams || Object.keys(queryParams).length === 0) return url;\n const separator = url.includes('?') ? '&' : '?';\n const params = new URLSearchParams(queryParams);\n return `${url}${separator}${params.toString()}`;\n }\n\n it('should append params to URL without existing params', () => {\n const result = appendQP('/app/data/record/Entity/123', { entity: 'Actions' });\n expect(result).toBe('/app/data/record/Entity/123?entity=Actions');\n });\n\n it('should append params to URL with existing params', () => {\n const result = appendQP('/app/data?existing=x', { entity: 'Actions' });\n expect(result).toBe('/app/data?existing=x&entity=Actions');\n });\n\n it('should return URL unchanged for empty params', () => {\n expect(appendQP('/app/data', {})).toBe('/app/data');\n });\n\n it('should return URL unchanged for undefined params', () => {\n expect(appendQP('/app/data', undefined)).toBe('/app/data');\n });\n\n it('should properly encode special characters', () => {\n const result = appendQP('/app/data', { entity: 'MJ: Actions' });\n expect(result).toContain('entity=MJ');\n // URLSearchParams encodes spaces as +\n expect(result).toMatch(/entity=MJ[+%].*Actions/);\n });\n\n it('should handle multiple params', () => {\n const result = appendQP('/app/data', { entity: 'Actions', filter: 'active', view: 'grid' });\n expect(result).toContain('entity=Actions');\n expect(result).toContain('filter=active');\n expect(result).toContain('view=grid');\n });\n});\n\n// ---- shouldReuseRoute logic tests ----\n\ndescribe('shouldReuseRoute logic', () => {\n // Test the comparison logic (routeConfig + params, NOT queryParams)\n function objectContentsEqual(obj1: Record<string, string>, obj2: Record<string, string>): boolean {\n if (obj1 === obj2) return true;\n if (!obj1 || !obj2) return false;\n const keys1 = Object.keys(obj1);\n const keys2 = Object.keys(obj2);\n if (keys1.length !== keys2.length) return false;\n return keys1.every(key => obj1[key] === obj2[key]);\n }\n\n function shouldReuseRoute(\n futureConfig: object | null,\n currConfig: object | null,\n futureParams: Record<string, string>,\n currParams: Record<string, string>\n ): boolean {\n return futureConfig === currConfig && objectContentsEqual(futureParams, currParams);\n }\n\n it('should return true for same config and params with different query params', () => {\n const config = {};\n // Query params intentionally excluded\n expect(shouldReuseRoute(config, config, { id: '1' }, { id: '1' })).toBe(true);\n });\n\n it('should return false for different path params', () => {\n const config = {};\n expect(shouldReuseRoute(config, config, { id: '1' }, { id: '2' })).toBe(false);\n });\n\n it('should return false for different route configs', () => {\n expect(shouldReuseRoute({}, {}, { id: '1' }, { id: '1' })).toBe(false);\n });\n\n it('should return true for same everything', () => {\n const config = {};\n expect(shouldReuseRoute(config, config, {}, {})).toBe(true);\n });\n\n it('should return false when one has params and other does not', () => {\n const config = {};\n expect(shouldReuseRoute(config, config, { id: '1' }, {})).toBe(false);\n });\n});\n\n// ---- Suppression flag behavior ----\n\ndescribe('_suppressQueryParamSync behavior', () => {\n it('should prevent UpdateQueryParams during suppression', () => {\n let suppressFlag = false;\n const mockUpdateActiveTabQP = vi.fn();\n\n function updateQueryParams(params: Record<string, string | null>): void {\n if (suppressFlag) return;\n mockUpdateActiveTabQP(params);\n }\n\n // Normal call\n updateQueryParams({ entity: 'Actions' });\n expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(1);\n\n // Suppressed call (simulates being inside OnQueryParamsChanged)\n suppressFlag = true;\n updateQueryParams({ entity: 'Members' });\n expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(1); // Still 1, not 2\n\n // After suppression cleared\n suppressFlag = false;\n updateQueryParams({ entity: 'Queries' });\n expect(mockUpdateActiveTabQP).toHaveBeenCalledTimes(2);\n });\n\n it('should clear suppression flag even if OnQueryParamsChanged throws', () => {\n let suppressFlag = false;\n\n function simulateSubscription(callback: () => void): void {\n suppressFlag = true;\n try {\n callback();\n } finally {\n suppressFlag = false;\n }\n }\n\n // The try/finally ensures flag is cleared even when callback throws\n try {\n simulateSubscription(() => {\n throw new Error('Component error');\n });\n } catch {\n // Expected — the error propagates but flag should still be cleared\n }\n\n // Flag should be cleared despite the error\n expect(suppressFlag).toBe(false);\n });\n});\n\n// ---- GetQueryParams behavior ----\n\ndescribe('GetQueryParams', () => {\n it('should return params from Configuration.queryParams', () => {\n const config: Record<string, unknown> = { queryParams: { entity: 'Actions', filter: 'active' } };\n const result = (config['queryParams'] as Record<string, string>) || {};\n expect(result).toEqual({ entity: 'Actions', filter: 'active' });\n });\n\n it('should return empty object when no queryParams', () => {\n const config: Record<string, unknown> = {};\n const result = (config['queryParams'] as Record<string, string>) || {};\n expect(result).toEqual({});\n });\n\n it('should return empty object when Configuration is null', () => {\n const config: Record<string, unknown> | null = null;\n const result = ((config?.['queryParams'] ?? {}) as Record<string, string>);\n expect(result).toEqual({});\n });\n});\n\n// ---- Tab-scoped filtering ----\n\ndescribe('Tab-scoped query param filtering', () => {\n it('should only deliver events matching the component tab ID', () => {\n const componentTabId = 'tab-abc';\n const events = [\n { TabId: 'tab-abc', Params: { entity: 'Actions' } },\n { TabId: 'tab-xyz', Params: { entity: 'Members' } },\n { TabId: 'tab-abc', Params: { entity: 'Queries' } },\n ];\n\n const received = events.filter(e => e.TabId === componentTabId);\n expect(received).toHaveLength(2);\n expect(received[0].Params.entity).toBe('Actions');\n expect(received[1].Params.entity).toBe('Queries');\n });\n\n it('should not deliver any events for non-matching tab ID', () => {\n const componentTabId = 'tab-none';\n const events = [\n { TabId: 'tab-abc', Params: { entity: 'Actions' } },\n { TabId: 'tab-xyz', Params: { entity: 'Members' } },\n ];\n\n const received = events.filter(e => e.TabId === componentTabId);\n expect(received).toHaveLength(0);\n });\n\n it('should handle empty tab ID gracefully', () => {\n const componentTabId = '';\n const events = [\n { TabId: 'tab-abc', Params: { entity: 'Actions' } },\n ];\n\n const received = events.filter(e => e.TabId === componentTabId);\n expect(received).toHaveLength(0);\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"base-dashboard.d.ts","sourceRoot":"","sources":["../../src/lib/base-dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAS,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;AAElE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,yBAAyB,CAAC;IACrC,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAED;;GAEG;AACH,8BACsB,aAAc,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IAC5F;;OAEG;IACH,IAAa,MAAM,CAAC,KAAK,EAAE,eAAe,EAEzC;IAED,IAAI,MAAM,IAAI,eAAe,GAAG,IAAI,CAEnC;IAED;;OAEG;IACO,KAAK,sBAA6B;IAE5C;;OAEG;IACO,gBAAgB,oBAA2B;IAErD;;OAEG;IACO,WAAW,oBAA2B;IAEhD;;OAEG;IACO,gBAAgB;oBAAiC,MAAM;oBAAc,YAAY;OAAK;IAEhG,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IAE3C,QAAQ;IAKd,WAAW,IAAI,IAAI;IAEnB;;OAEG;IACI,OAAO,IAAI,IAAI;IAItB,OAAO,CAAC,QAAQ,CAAkB;IAClC;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,IAAI,IAAI;IAExC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI;IAEnC;;;;OAIG;IACG,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;yCAvE3C,aAAa;2CAAb,aAAa;CA0ElC"}
1
+ {"version":3,"file":"base-dashboard.d.ts","sourceRoot":"","sources":["../../src/lib/base-dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAS,SAAS,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;AAElE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,yBAAyB,CAAC;IACrC,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAED;;GAEG;AACH,8BACsB,aAAc,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IAC5F;;OAEG;IACH,IAAa,MAAM,CAAC,KAAK,EAAE,eAAe,EAEzC;IAED,IAAI,MAAM,IAAI,eAAe,GAAG,IAAI,CAEnC;IAED;;OAEG;IACO,KAAK,sBAA6B;IAE5C;;OAEG;IACO,gBAAgB,oBAA2B;IAErD;;OAEG;IACO,WAAW,oBAA2B;IAEhD;;OAEG;IACO,gBAAgB;oBAAiC,MAAM;oBAAc,YAAY;OAAK;IAEhG,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IAE3C,QAAQ;IAOd,WAAW,IAAI,IAAI;IAInB;;OAEG;IACI,OAAO,IAAI,IAAI;IAItB,OAAO,CAAC,QAAQ,CAAkB;IAClC;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,IAAI,IAAI;IAExC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI;IAEnC;;;;OAIG;IACG,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;yCA3E3C,aAAa;2CAAb,aAAa;CA8ElC"}
@@ -32,10 +32,14 @@ export class BaseDashboard extends BaseResourceComponent {
32
32
  OpenEntityRecord = new EventEmitter();
33
33
  _config = null;
34
34
  async ngOnInit() {
35
+ super.ngOnInit();
35
36
  this.initDashboard();
36
37
  await this.loadData();
38
+ this.NotifyLoadComplete();
39
+ }
40
+ ngOnDestroy() {
41
+ super.ngOnDestroy();
37
42
  }
38
- ngOnDestroy() { }
39
43
  /**
40
44
  * This method will result in the dashboard being reloaded.
41
45
  */
@@ -1 +1 @@
1
- {"version":3,"file":"base-dashboard.js","sourceRoot":"","sources":["../../src/lib/base-dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAqB,MAAM,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;AAOlE;;GAEG;AAEH,MAAM,OAAgB,aAAc,SAAQ,qBAAqB;IAC/D;;OAEG;IACH,IAAa,MAAM,CAAC,KAAsB;QACxC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACO,KAAK,GAAG,IAAI,YAAY,EAAS,CAAC;IAE5C;;OAEG;IACO,gBAAgB,GAAG,IAAI,YAAY,EAAO,CAAC;IAErD;;OAEG;IACO,WAAW,GAAG,IAAI,YAAY,EAAO,CAAC;IAEhD;;OAEG;IACO,gBAAgB,GAAG,IAAI,YAAY,EAAkD,CAAC;IAEtF,OAAO,GAA2B,IAAI,CAAC;IAEjD,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAED,WAAW,KAAU,CAAC;IAEtB;;OAEG;IACI,OAAO;QACZ,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAEO,QAAQ,GAAY,KAAK,CAAC;IAClC;;OAEG;IACI,UAAU,CAAC,OAAgB;QAChC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAYD;;;;OAIG;IACH,KAAK,CAAC,oBAAoB,CAAC,IAAkB;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;iOAzEmB,aAAa,yBAAb,aAAa;6DAAb,aAAa;;iFAAb,aAAa;cADlC,SAAS;;kBAKP,KAAK;;kBAWL,MAAM;;kBAKN,MAAM;;kBAKN,MAAM;;kBAKN,MAAM","sourcesContent":["import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';\nimport { CompositeKey } from '@memberjunction/core';\nimport { MJDashboardEntityExtended, ResourceData } from '@memberjunction/core-entities';\nimport { BaseResourceComponent } from './base-resource-component';\n\nexport interface DashboardConfig {\n dashboard: MJDashboardEntityExtended;\n userState?: any;\n}\n\n/**\n * Make the base class a directive so we can use Angular functionality and sub-classes can be @Component \n */\n@Directive()\nexport abstract class BaseDashboard extends BaseResourceComponent implements OnInit, OnDestroy {\n /**\n * Set or change the dashboard configuration. Changing this property will NOT cause the dashboard to reload. Call Refresh() to do that.\n */\n @Input() set Config(value: DashboardConfig) {\n this._config = value;\n }\n\n get Config(): DashboardConfig | null {\n return this._config;\n }\n\n /**\n * Subclasses can emit anytime an error occurs. \n */\n @Output() Error = new EventEmitter<Error>();\n\n /**\n * Subclasses should emit this event anytime their internal state changes in a way that they'd like to persist.\n */\n @Output() UserStateChanged = new EventEmitter<any>();\n\n /**\n * Subclasses can emit this event anytime they want to communicate with the container to let it know that something has happened of significance.\n */\n @Output() Interaction = new EventEmitter<any>();\n\n /**\n * Subclasses can emit this event anytime they want to open a record within a particular entity. The container should handle this event and open the record.\n */\n @Output() OpenEntityRecord = new EventEmitter<{EntityName: string, RecordPKey: CompositeKey}>();\n\n protected _config: DashboardConfig | null = null;\n\n async ngOnInit() {\n this.initDashboard();\n await this.loadData();\n }\n\n ngOnDestroy(): void {}\n\n /**\n * This method will result in the dashboard being reloaded.\n */\n public Refresh(): void {\n this.loadData();\n }\n\n private _visible: boolean = false;\n /**\n * This method can be used by a container to let the dashboard know that it is being opened/closed. Base class just sets a flag.\n */\n public SetVisible(visible: boolean): void {\n this._visible = visible;\n }\n\n /**\n * Subclasses can override this method to perform any initialization they need. This method only runs once when the dashboard is created.\n */\n protected abstract initDashboard(): void;\n\n /**\n * Subclasses should override this method to load their data. This method is called when the dashboard is created and when Refresh() is called.\n */\n protected abstract loadData(): void;\n\n /**\n * Sub-classes can override this to provide a custom icon class\n * @param data \n * @returns \n */\n async GetResourceIconClass(data: ResourceData): Promise<string> {\n return \"\";\n }\n} "]}
1
+ {"version":3,"file":"base-dashboard.js","sourceRoot":"","sources":["../../src/lib/base-dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAqB,MAAM,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;AAOlE;;GAEG;AAEH,MAAM,OAAgB,aAAc,SAAQ,qBAAqB;IAC/D;;OAEG;IACH,IAAa,MAAM,CAAC,KAAsB;QACxC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACO,KAAK,GAAG,IAAI,YAAY,EAAS,CAAC;IAE5C;;OAEG;IACO,gBAAgB,GAAG,IAAI,YAAY,EAAO,CAAC;IAErD;;OAEG;IACO,WAAW,GAAG,IAAI,YAAY,EAAO,CAAC;IAEhD;;OAEG;IACO,gBAAgB,GAAG,IAAI,YAAY,EAAkD,CAAC;IAEtF,OAAO,GAA2B,IAAI,CAAC;IAEjD,KAAK,CAAC,QAAQ;QACZ,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED,WAAW;QACT,KAAK,CAAC,WAAW,EAAE,CAAC;IACtB,CAAC;IAED;;OAEG;IACI,OAAO;QACZ,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAEO,QAAQ,GAAY,KAAK,CAAC;IAClC;;OAEG;IACI,UAAU,CAAC,OAAgB;QAChC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAYD;;;;OAIG;IACH,KAAK,CAAC,oBAAoB,CAAC,IAAkB;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;iOA7EmB,aAAa,yBAAb,aAAa;6DAAb,aAAa;;iFAAb,aAAa;cADlC,SAAS;;kBAKP,KAAK;;kBAWL,MAAM;;kBAKN,MAAM;;kBAKN,MAAM;;kBAKN,MAAM","sourcesContent":["import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';\nimport { CompositeKey } from '@memberjunction/core';\nimport { MJDashboardEntityExtended, ResourceData } from '@memberjunction/core-entities';\nimport { BaseResourceComponent } from './base-resource-component';\n\nexport interface DashboardConfig {\n dashboard: MJDashboardEntityExtended;\n userState?: any;\n}\n\n/**\n * Make the base class a directive so we can use Angular functionality and sub-classes can be @Component \n */\n@Directive()\nexport abstract class BaseDashboard extends BaseResourceComponent implements OnInit, OnDestroy {\n /**\n * Set or change the dashboard configuration. Changing this property will NOT cause the dashboard to reload. Call Refresh() to do that.\n */\n @Input() set Config(value: DashboardConfig) {\n this._config = value;\n }\n\n get Config(): DashboardConfig | null {\n return this._config;\n }\n\n /**\n * Subclasses can emit anytime an error occurs. \n */\n @Output() Error = new EventEmitter<Error>();\n\n /**\n * Subclasses should emit this event anytime their internal state changes in a way that they'd like to persist.\n */\n @Output() UserStateChanged = new EventEmitter<any>();\n\n /**\n * Subclasses can emit this event anytime they want to communicate with the container to let it know that something has happened of significance.\n */\n @Output() Interaction = new EventEmitter<any>();\n\n /**\n * Subclasses can emit this event anytime they want to open a record within a particular entity. The container should handle this event and open the record.\n */\n @Output() OpenEntityRecord = new EventEmitter<{EntityName: string, RecordPKey: CompositeKey}>();\n\n protected _config: DashboardConfig | null = null;\n\n async ngOnInit() {\n super.ngOnInit();\n this.initDashboard();\n await this.loadData();\n this.NotifyLoadComplete();\n }\n\n ngOnDestroy(): void {\n super.ngOnDestroy();\n }\n\n /**\n * This method will result in the dashboard being reloaded.\n */\n public Refresh(): void {\n this.loadData();\n }\n\n private _visible: boolean = false;\n /**\n * This method can be used by a container to let the dashboard know that it is being opened/closed. Base class just sets a flag.\n */\n public SetVisible(visible: boolean): void {\n this._visible = visible;\n }\n\n /**\n * Subclasses can override this method to perform any initialization they need. This method only runs once when the dashboard is created.\n */\n protected abstract initDashboard(): void;\n\n /**\n * Subclasses should override this method to load their data. This method is called when the dashboard is created and when Refresh() is called.\n */\n protected abstract loadData(): void;\n\n /**\n * Sub-classes can override this to provide a custom icon class\n * @param data \n * @returns \n */\n async GetResourceIconClass(data: ResourceData): Promise<string> {\n return \"\";\n }\n} "]}
@@ -1,8 +1,21 @@
1
+ import { OnInit, OnDestroy } from "@angular/core";
2
+ import { Subject } from "rxjs";
1
3
  import { BaseEntity } from "@memberjunction/core";
2
4
  import { BaseNavigationComponent } from "./base-navigation-component";
3
5
  import { ResourceData } from "@memberjunction/core-entities";
4
- export declare abstract class BaseResourceComponent extends BaseNavigationComponent {
6
+ import { NavigationService } from "./navigation.service";
7
+ import * as i0 from "@angular/core";
8
+ export declare abstract class BaseResourceComponent extends BaseNavigationComponent implements OnInit, OnDestroy {
5
9
  private _data;
10
+ private _suppressQueryParamSync;
11
+ protected destroy$: Subject<void>;
12
+ protected navigationService: NavigationService;
13
+ /**
14
+ * Tab ID for query param notification scoping. Set by resource wrappers
15
+ * that render child dashboards, so the child knows which tab it belongs to.
16
+ * If not set, falls back to Data.Configuration.tabId.
17
+ */
18
+ ParentTabId: string | null;
6
19
  get Data(): ResourceData;
7
20
  set Data(value: ResourceData);
8
21
  private _loadComplete;
@@ -21,6 +34,36 @@ export declare abstract class BaseResourceComponent extends BaseNavigationCompon
21
34
  private _displayNameChangedEvent;
22
35
  get DisplayNameChangedEvent(): ((newName: string) => void) | null;
23
36
  set DisplayNameChangedEvent(value: ((newName: string) => void) | null);
37
+ ngOnInit(): void;
38
+ ngOnDestroy(): void;
39
+ /**
40
+ * Called by the framework when query params change from an external source
41
+ * (browser back/forward, deep link navigation).
42
+ * Override in subclasses to react to query param changes.
43
+ * @param params The new query params from the URL
44
+ * @param source 'popstate' for back/forward, 'deeplink' for external URL entry
45
+ */
46
+ protected OnQueryParamsChanged(params: Record<string, string>, source: 'popstate' | 'deeplink'): void;
47
+ /**
48
+ * Push query param changes to the URL. Creates a browser history entry.
49
+ * Safe to call during OnQueryParamsChanged — auto-suppressed to prevent loops.
50
+ */
51
+ protected UpdateQueryParams(params: Record<string, string | null>): void;
52
+ /**
53
+ * Read current query params from tab configuration.
54
+ * Use in initDashboard() / ngOnInit() to get initial URL state.
55
+ */
56
+ protected GetQueryParams(): Record<string, string>;
57
+ /**
58
+ * Internal: subscribe to NavigationService query param notifications.
59
+ * Filters to only this component's tab to prevent cross-tab leakage.
60
+ */
61
+ private setupQueryParamSubscription;
62
+ /**
63
+ * Get this component's tab ID. Checks ParentTabId input first (set by resource
64
+ * wrappers for child dashboards), then falls back to Data.Configuration.tabId.
65
+ */
66
+ getTabId(): string;
24
67
  protected NotifyLoadComplete(): void;
25
68
  protected NotifyLoadStarted(): void;
26
69
  /**
@@ -31,5 +74,7 @@ export declare abstract class BaseResourceComponent extends BaseNavigationCompon
31
74
  protected ResourceRecordSaved(resourceRecordEntity: BaseEntity): void;
32
75
  abstract GetResourceDisplayName(data: ResourceData): Promise<string>;
33
76
  abstract GetResourceIconClass(data: ResourceData): Promise<string>;
77
+ static ɵfac: i0.ɵɵFactoryDeclaration<BaseResourceComponent, never>;
78
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BaseResourceComponent, never, never, { "ParentTabId": { "alias": "ParentTabId"; "required": false; }; }, {}, never, never, true, never>;
34
79
  }
35
80
  //# sourceMappingURL=base-resource-component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-resource-component.d.ts","sourceRoot":"","sources":["../../src/lib/base-resource-component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,8BAAsB,qBAAsB,SAAQ,uBAAuB;IACvE,OAAO,CAAC,KAAK,CAAoC;IAEjD,IAAW,IAAI,IAAI,YAAY,CAE9B;IACD,IAAW,IAAI,CAAC,KAAK,EAAE,YAAY,EAElC;IAED,OAAO,CAAC,aAAa,CAAkB;IACvC,IAAW,YAAY,IAAI,OAAO,CAEjC;IAED,OAAO,CAAC,YAAY,CAAkB;IACtC,IAAW,WAAW,IAAI,OAAO,CAEhC;IAGD,OAAO,CAAC,kBAAkB,CAAa;IACvC,IAAW,iBAAiB,IAAI,GAAG,CAElC;IACD,IAAW,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAEtC;IAED,OAAO,CAAC,iBAAiB,CAAa;IACtC,IAAW,gBAAgB,IAAI,GAAG,CAEjC;IACD,IAAW,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAErC;IAED,OAAO,CAAC,yBAAyB,CAAa;IAC9C,IAAW,wBAAwB,IAAI,GAAG,CAEzC;IACD,IAAW,wBAAwB,CAAC,KAAK,EAAE,GAAG,EAE7C;IAED,OAAO,CAAC,wBAAwB,CAA4C;IAC5E,IAAW,uBAAuB,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAEvE;IACD,IAAW,uBAAuB,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,EAE3E;IAED,SAAS,CAAC,kBAAkB;IAO5B,SAAS,CAAC,iBAAiB;IAS3B;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMzD,SAAS,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,UAAU;IAO9D,QAAQ,CAAC,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAEpE,QAAQ,CAAC,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;CACrE"}
1
+ {"version":3,"file":"base-resource-component.d.ts","sourceRoot":"","sources":["../../src/lib/base-resource-component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,SAAS,EAAiB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;AAEzD,8BACsB,qBAAsB,SAAQ,uBAAwB,YAAW,MAAM,EAAE,SAAS;IACpG,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,uBAAuB,CAAS;IACxC,SAAS,CAAC,QAAQ,gBAAuB;IACzC,SAAS,CAAC,iBAAiB,oBAA6B;IAExD;;;;OAIG;IACM,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE3C,IAAW,IAAI,IAAI,YAAY,CAE9B;IACD,IAAW,IAAI,CAAC,KAAK,EAAE,YAAY,EAElC;IAED,OAAO,CAAC,aAAa,CAAkB;IACvC,IAAW,YAAY,IAAI,OAAO,CAEjC;IAED,OAAO,CAAC,YAAY,CAAkB;IACtC,IAAW,WAAW,IAAI,OAAO,CAEhC;IAGD,OAAO,CAAC,kBAAkB,CAAa;IACvC,IAAW,iBAAiB,IAAI,GAAG,CAElC;IACD,IAAW,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAEtC;IAED,OAAO,CAAC,iBAAiB,CAAa;IACtC,IAAW,gBAAgB,IAAI,GAAG,CAEjC;IACD,IAAW,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAErC;IAED,OAAO,CAAC,yBAAyB,CAAa;IAC9C,IAAW,wBAAwB,IAAI,GAAG,CAEzC;IACD,IAAW,wBAAwB,CAAC,KAAK,EAAE,GAAG,EAE7C;IAED,OAAO,CAAC,wBAAwB,CAA4C;IAC5E,IAAW,uBAAuB,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAEvE;IACD,IAAW,uBAAuB,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,EAE3E;IAED,QAAQ,IAAI,IAAI;IAIhB,WAAW,IAAI,IAAI;IAKnB;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAIrG;;;OAGG;IACH,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI;IAKxE;;;OAGG;IACH,SAAS,CAAC,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIlD;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAqBnC;;;OAGG;IACI,QAAQ,IAAI,MAAM;IAIzB,SAAS,CAAC,kBAAkB;IAO5B,SAAS,CAAC,iBAAiB;IAS3B;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMzD,SAAS,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,UAAU;IAO9D,QAAQ,CAAC,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAEpE,QAAQ,CAAC,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;yCAxKhD,qBAAqB;2CAArB,qBAAqB;CAyK1C"}
@@ -1,7 +1,21 @@
1
+ import { Directive, Input, inject } from "@angular/core";
2
+ import { Subject } from "rxjs";
3
+ import { filter, takeUntil } from "rxjs/operators";
1
4
  import { BaseNavigationComponent } from "./base-navigation-component";
2
5
  import { ResourceData } from "@memberjunction/core-entities";
6
+ import { NavigationService } from "./navigation.service";
7
+ import * as i0 from "@angular/core";
3
8
  export class BaseResourceComponent extends BaseNavigationComponent {
4
9
  _data = new ResourceData();
10
+ _suppressQueryParamSync = false;
11
+ destroy$ = new Subject();
12
+ navigationService = inject(NavigationService);
13
+ /**
14
+ * Tab ID for query param notification scoping. Set by resource wrappers
15
+ * that render child dashboards, so the child knows which tab it belongs to.
16
+ * If not set, falls back to Data.Configuration.tabId.
17
+ */
18
+ ParentTabId = null;
5
19
  get Data() {
6
20
  return this._data;
7
21
  }
@@ -44,6 +58,68 @@ export class BaseResourceComponent extends BaseNavigationComponent {
44
58
  set DisplayNameChangedEvent(value) {
45
59
  this._displayNameChangedEvent = value;
46
60
  }
61
+ ngOnInit() {
62
+ this.setupQueryParamSubscription();
63
+ }
64
+ ngOnDestroy() {
65
+ this.destroy$.next();
66
+ this.destroy$.complete();
67
+ }
68
+ /**
69
+ * Called by the framework when query params change from an external source
70
+ * (browser back/forward, deep link navigation).
71
+ * Override in subclasses to react to query param changes.
72
+ * @param params The new query params from the URL
73
+ * @param source 'popstate' for back/forward, 'deeplink' for external URL entry
74
+ */
75
+ OnQueryParamsChanged(params, source) {
76
+ // Default no-op — override in subclasses
77
+ }
78
+ /**
79
+ * Push query param changes to the URL. Creates a browser history entry.
80
+ * Safe to call during OnQueryParamsChanged — auto-suppressed to prevent loops.
81
+ */
82
+ UpdateQueryParams(params) {
83
+ if (this._suppressQueryParamSync)
84
+ return;
85
+ this.navigationService.UpdateActiveTabQueryParams(params);
86
+ }
87
+ /**
88
+ * Read current query params from tab configuration.
89
+ * Use in initDashboard() / ngOnInit() to get initial URL state.
90
+ */
91
+ GetQueryParams() {
92
+ return this.Data?.Configuration?.['queryParams'] || {};
93
+ }
94
+ /**
95
+ * Internal: subscribe to NavigationService query param notifications.
96
+ * Filters to only this component's tab to prevent cross-tab leakage.
97
+ */
98
+ setupQueryParamSubscription() {
99
+ this.navigationService.QueryParamChanged$
100
+ .pipe(filter(event => {
101
+ const myTabId = this.getTabId();
102
+ return !myTabId || event.TabId === myTabId;
103
+ }), takeUntil(this.destroy$))
104
+ .subscribe(event => {
105
+ // try/finally ensures suppression flag is always cleared,
106
+ // even if OnQueryParamsChanged throws
107
+ this._suppressQueryParamSync = true;
108
+ try {
109
+ this.OnQueryParamsChanged(event.Params, 'popstate');
110
+ }
111
+ finally {
112
+ this._suppressQueryParamSync = false;
113
+ }
114
+ });
115
+ }
116
+ /**
117
+ * Get this component's tab ID. Checks ParentTabId input first (set by resource
118
+ * wrappers for child dashboards), then falls back to Data.Configuration.tabId.
119
+ */
120
+ getTabId() {
121
+ return this.ParentTabId || this.Data?.Configuration?.['tabId'] || '';
122
+ }
47
123
  NotifyLoadComplete() {
48
124
  this._loadComplete = true;
49
125
  if (this._loadCompleteEvent) {
@@ -71,5 +147,12 @@ export class BaseResourceComponent extends BaseNavigationComponent {
71
147
  this._resourceRecordSavedEvent(resourceRecordEntity);
72
148
  }
73
149
  }
150
+ static ɵfac = /*@__PURE__*/ (() => { let ɵBaseResourceComponent_BaseFactory; return function BaseResourceComponent_Factory(__ngFactoryType__) { return (ɵBaseResourceComponent_BaseFactory || (ɵBaseResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(BaseResourceComponent)))(__ngFactoryType__ || BaseResourceComponent); }; })();
151
+ static ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseResourceComponent, inputs: { ParentTabId: "ParentTabId" }, features: [i0.ɵɵInheritDefinitionFeature] });
74
152
  }
153
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseResourceComponent, [{
154
+ type: Directive
155
+ }], null, { ParentTabId: [{
156
+ type: Input
157
+ }] }); })();
75
158
  //# sourceMappingURL=base-resource-component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-resource-component.js","sourceRoot":"","sources":["../../src/lib/base-resource-component.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,OAAgB,qBAAsB,SAAQ,uBAAuB;IAC/D,KAAK,GAAiB,IAAI,YAAY,EAAE,CAAC;IAEjD,IAAW,IAAI;QACX,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IACD,IAAW,IAAI,CAAC,KAAmB;QAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAEO,aAAa,GAAY,KAAK,CAAC;IACvC,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IAEO,YAAY,GAAY,KAAK,CAAC;IACtC,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAGO,kBAAkB,GAAQ,IAAI,CAAC;IACvC,IAAW,iBAAiB;QACxB,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAClC,CAAC;IACD,IAAW,iBAAiB,CAAC,KAAU;QACnC,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;IACpC,CAAC;IAEO,iBAAiB,GAAQ,IAAI,CAAC;IACtC,IAAW,gBAAgB;QACvB,OAAO,IAAI,CAAC,iBAAiB,CAAA;IACjC,CAAC;IACD,IAAW,gBAAgB,CAAC,KAAU;QAClC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;IACnC,CAAC;IAEO,yBAAyB,GAAQ,IAAI,CAAC;IAC9C,IAAW,wBAAwB;QAC/B,OAAO,IAAI,CAAC,yBAAyB,CAAA;IACzC,CAAC;IACD,IAAW,wBAAwB,CAAC,KAAU;QAC1C,IAAI,CAAC,yBAAyB,GAAG,KAAK,CAAC;IAC3C,CAAC;IAEO,wBAAwB,GAAuC,IAAI,CAAC;IAC5E,IAAW,uBAAuB;QAC9B,OAAO,IAAI,CAAC,wBAAwB,CAAC;IACzC,CAAC;IACD,IAAW,uBAAuB,CAAC,KAAyC;QACxE,IAAI,CAAC,wBAAwB,GAAG,KAAK,CAAC;IAC1C,CAAC;IAES,kBAAkB;QACxB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;IAES,iBAAiB;QACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;IAID;;;OAGG;IACO,wBAAwB,CAAC,OAAe;QAC9C,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAChC,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;IACL,CAAC;IAES,mBAAmB,CAAC,oBAAgC;QAC1D,IAAI,CAAC,IAAI,CAAC,gBAAgB,GAAG,oBAAoB,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACxE,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACjC,IAAI,CAAC,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;QACzD,CAAC;IACL,CAAC;CAKJ","sourcesContent":["import { BaseEntity } from \"@memberjunction/core\";\nimport { SharedService } from \"./shared.service\";\nimport { BaseNavigationComponent } from \"./base-navigation-component\";\nimport { ResourceData } from \"@memberjunction/core-entities\";\n\nexport abstract class BaseResourceComponent extends BaseNavigationComponent {\n private _data: ResourceData = new ResourceData();\n\n public get Data(): ResourceData {\n return this._data;\n }\n public set Data(value: ResourceData) {\n this._data = value;\n }\n\n private _loadComplete: boolean = false;\n public get LoadComplete(): boolean {\n return this._loadComplete;\n }\n\n private _loadStarted: boolean = false;\n public get LoadStarted(): boolean {\n return this._loadStarted;\n }\n\n \n private _loadCompleteEvent: any = null;\n public get LoadCompleteEvent(): any {\n return this._loadCompleteEvent\n }\n public set LoadCompleteEvent(value: any) {\n this._loadCompleteEvent = value;\n }\n\n private _loadStartedEvent: any = null;\n public get LoadStartedEvent(): any {\n return this._loadStartedEvent\n }\n public set LoadStartedEvent(value: any) {\n this._loadStartedEvent = value;\n }\n\n private _resourceRecordSavedEvent: any = null;\n public get ResourceRecordSavedEvent(): any {\n return this._resourceRecordSavedEvent\n }\n public set ResourceRecordSavedEvent(value: any) {\n this._resourceRecordSavedEvent = value;\n }\n\n private _displayNameChangedEvent: ((newName: string) => void) | null = null;\n public get DisplayNameChangedEvent(): ((newName: string) => void) | null {\n return this._displayNameChangedEvent;\n }\n public set DisplayNameChangedEvent(value: ((newName: string) => void) | null) {\n this._displayNameChangedEvent = value;\n }\n\n protected NotifyLoadComplete() {\n this._loadComplete = true;\n if (this._loadCompleteEvent) {\n this._loadCompleteEvent();\n }\n }\n\n protected NotifyLoadStarted() {\n this._loadStarted = true;\n if (this._loadStartedEvent) {\n this._loadStartedEvent();\n }\n }\n \n \n\n /**\n * Call this to notify the tab system that the resource's display name has changed.\n * The tab container will update the tab title and browser title accordingly.\n */\n protected NotifyDisplayNameChanged(newName: string): void {\n if (this._displayNameChangedEvent) {\n this._displayNameChangedEvent(newName);\n }\n }\n\n protected ResourceRecordSaved(resourceRecordEntity: BaseEntity) {\n this.Data.ResourceRecordID = resourceRecordEntity.PrimaryKey.ToString();\n if (this._resourceRecordSavedEvent) {\n this._resourceRecordSavedEvent(resourceRecordEntity);\n }\n }\n\n abstract GetResourceDisplayName(data: ResourceData): Promise<string>\n\n abstract GetResourceIconClass(data: ResourceData): Promise<string>\n}\n"]}
1
+ {"version":3,"file":"base-resource-component.js","sourceRoot":"","sources":["../../src/lib/base-resource-component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, OnInit, OnDestroy, Input, inject } from \"@angular/core\";\nimport { Subject } from \"rxjs\";\nimport { filter, takeUntil } from \"rxjs/operators\";\nimport { BaseEntity } from \"@memberjunction/core\";\nimport { BaseNavigationComponent } from \"./base-navigation-component\";\nimport { ResourceData } from \"@memberjunction/core-entities\";\nimport { NavigationService } from \"./navigation.service\";\n\n@Directive()\nexport abstract class BaseResourceComponent extends BaseNavigationComponent implements OnInit, OnDestroy {\n private _data: ResourceData = new ResourceData();\n private _suppressQueryParamSync = false;\n protected destroy$ = new Subject<void>();\n protected navigationService = inject(NavigationService);\n\n /**\n * Tab ID for query param notification scoping. Set by resource wrappers\n * that render child dashboards, so the child knows which tab it belongs to.\n * If not set, falls back to Data.Configuration.tabId.\n */\n @Input() ParentTabId: string | null = null;\n\n public get Data(): ResourceData {\n return this._data;\n }\n public set Data(value: ResourceData) {\n this._data = value;\n }\n\n private _loadComplete: boolean = false;\n public get LoadComplete(): boolean {\n return this._loadComplete;\n }\n\n private _loadStarted: boolean = false;\n public get LoadStarted(): boolean {\n return this._loadStarted;\n }\n\n\n private _loadCompleteEvent: any = null;\n public get LoadCompleteEvent(): any {\n return this._loadCompleteEvent\n }\n public set LoadCompleteEvent(value: any) {\n this._loadCompleteEvent = value;\n }\n\n private _loadStartedEvent: any = null;\n public get LoadStartedEvent(): any {\n return this._loadStartedEvent\n }\n public set LoadStartedEvent(value: any) {\n this._loadStartedEvent = value;\n }\n\n private _resourceRecordSavedEvent: any = null;\n public get ResourceRecordSavedEvent(): any {\n return this._resourceRecordSavedEvent\n }\n public set ResourceRecordSavedEvent(value: any) {\n this._resourceRecordSavedEvent = value;\n }\n\n private _displayNameChangedEvent: ((newName: string) => void) | null = null;\n public get DisplayNameChangedEvent(): ((newName: string) => void) | null {\n return this._displayNameChangedEvent;\n }\n public set DisplayNameChangedEvent(value: ((newName: string) => void) | null) {\n this._displayNameChangedEvent = value;\n }\n\n ngOnInit(): void {\n this.setupQueryParamSubscription();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * Called by the framework when query params change from an external source\n * (browser back/forward, deep link navigation).\n * Override in subclasses to react to query param changes.\n * @param params The new query params from the URL\n * @param source 'popstate' for back/forward, 'deeplink' for external URL entry\n */\n protected OnQueryParamsChanged(params: Record<string, string>, source: 'popstate' | 'deeplink'): void {\n // Default no-op — override in subclasses\n }\n\n /**\n * Push query param changes to the URL. Creates a browser history entry.\n * Safe to call during OnQueryParamsChanged — auto-suppressed to prevent loops.\n */\n protected UpdateQueryParams(params: Record<string, string | null>): void {\n if (this._suppressQueryParamSync) return;\n this.navigationService.UpdateActiveTabQueryParams(params);\n }\n\n /**\n * Read current query params from tab configuration.\n * Use in initDashboard() / ngOnInit() to get initial URL state.\n */\n protected GetQueryParams(): Record<string, string> {\n return (this.Data?.Configuration?.['queryParams'] as Record<string, string>) || {};\n }\n\n /**\n * Internal: subscribe to NavigationService query param notifications.\n * Filters to only this component's tab to prevent cross-tab leakage.\n */\n private setupQueryParamSubscription(): void {\n this.navigationService.QueryParamChanged$\n .pipe(\n filter(event => {\n const myTabId = this.getTabId();\n return !myTabId || event.TabId === myTabId;\n }),\n takeUntil(this.destroy$)\n )\n .subscribe(event => {\n // try/finally ensures suppression flag is always cleared,\n // even if OnQueryParamsChanged throws\n this._suppressQueryParamSync = true;\n try {\n this.OnQueryParamsChanged(event.Params, 'popstate');\n } finally {\n this._suppressQueryParamSync = false;\n }\n });\n }\n\n /**\n * Get this component's tab ID. Checks ParentTabId input first (set by resource\n * wrappers for child dashboards), then falls back to Data.Configuration.tabId.\n */\n public getTabId(): string {\n return this.ParentTabId || this.Data?.Configuration?.['tabId'] as string || '';\n }\n\n protected NotifyLoadComplete() {\n this._loadComplete = true;\n if (this._loadCompleteEvent) {\n this._loadCompleteEvent();\n }\n }\n\n protected NotifyLoadStarted() {\n this._loadStarted = true;\n if (this._loadStartedEvent) {\n this._loadStartedEvent();\n }\n }\n\n\n\n /**\n * Call this to notify the tab system that the resource's display name has changed.\n * The tab container will update the tab title and browser title accordingly.\n */\n protected NotifyDisplayNameChanged(newName: string): void {\n if (this._displayNameChangedEvent) {\n this._displayNameChangedEvent(newName);\n }\n }\n\n protected ResourceRecordSaved(resourceRecordEntity: BaseEntity) {\n this.Data.ResourceRecordID = resourceRecordEntity.PrimaryKey.ToString();\n if (this._resourceRecordSavedEvent) {\n this._resourceRecordSavedEvent(resourceRecordEntity);\n }\n }\n\n abstract GetResourceDisplayName(data: ResourceData): Promise<string>\n\n abstract GetResourceIconClass(data: ResourceData): Promise<string>\n}\n"]}
@@ -2,7 +2,36 @@ import { OnDestroy } from '@angular/core';
2
2
  import { WorkspaceStateManager, NavItem, ApplicationManager } from '@memberjunction/ng-base-application';
3
3
  import { NavigationOptions } from './navigation.interfaces';
4
4
  import { CompositeKey } from '@memberjunction/core';
5
+ import { Subject } from 'rxjs';
6
+ import { BaseResourceComponent } from './base-resource-component';
5
7
  import * as i0 from "@angular/core";
8
+ /**
9
+ * Event emitted when query params change on a tab (e.g., from browser back/forward).
10
+ * Includes the tab ID so that only the component in the affected tab reacts,
11
+ * preventing cross-tab leakage in multi-tab scenarios.
12
+ */
13
+ export interface QueryParamChangeEvent {
14
+ TabId: string;
15
+ Params: Record<string, string>;
16
+ }
17
+ /**
18
+ * Event emitted when a resource component reports its agent context or tools.
19
+ * The shell (which owns the ComponentCacheManager) subscribes to these events
20
+ * and updates the cache + active AppContextSnapshot accordingly.
21
+ */
22
+ export interface AgentContextUpdate {
23
+ /** The component instance that reported the update */
24
+ Caller: BaseResourceComponent;
25
+ /** Dashboard-specific context for the agent (undefined = no change) */
26
+ AgentContext?: Record<string, unknown>;
27
+ /** Client tools available from this dashboard (undefined = no change) */
28
+ AgentClientTools?: Array<{
29
+ Name: string;
30
+ Description: string;
31
+ ParameterSchema: Record<string, unknown>;
32
+ Handler: (params: Record<string, unknown>) => Promise<unknown>;
33
+ }>;
34
+ }
6
35
  /**
7
36
  * System application ID for non-app-specific resources (fallback only)
8
37
  * Uses double underscore prefix to indicate system-level resource
@@ -18,6 +47,9 @@ export declare class NavigationService implements OnDestroy {
18
47
  private appManager;
19
48
  private shiftKeyPressed;
20
49
  private subscriptions;
50
+ private queryParamChanged$;
51
+ /** Observable that emits when query params change on a tab (back/forward navigation). */
52
+ QueryParamChanged$: import("rxjs").Observable<QueryParamChangeEvent>;
21
53
  /** Cached Home app ID (null means not found, undefined means not checked) */
22
54
  private _homeAppId;
23
55
  /** Cached Home app color */
@@ -47,6 +79,40 @@ export declare class NavigationService implements OnDestroy {
47
79
  * Call this if apps are reloaded or user logs out.
48
80
  */
49
81
  clearHomeAppCache(): void;
82
+ /**
83
+ * Observable stream of agent context updates from resource components.
84
+ * The shell subscribes to this to update the ComponentCacheManager and
85
+ * push changes to the chat overlay's AppContextSnapshot.DashboardContext.
86
+ */
87
+ readonly AgentContextUpdated$: Subject<AgentContextUpdate>;
88
+ /**
89
+ * Report the current agent-visible state from a resource component.
90
+ * Call this whenever the dashboard's internal state changes (tab switch,
91
+ * filter change, pipeline status change, drill-down, etc.).
92
+ *
93
+ * @param caller - Pass `this` from the calling component. Used to match
94
+ * against the ComponentCacheManager to identify which cached component
95
+ * this update belongs to.
96
+ * @param context - Key-value pairs representing dashboard state the agent
97
+ * should know about. Each dashboard defines its own shape.
98
+ */
99
+ SetAgentContext(caller: BaseResourceComponent, context: Record<string, unknown>): void;
100
+ /**
101
+ * Register the client tools available from a resource component.
102
+ * Call this on component init and whenever the available tools change.
103
+ * Tools are automatically unregistered when the component becomes inactive
104
+ * (tab switch) and re-registered when it becomes active again.
105
+ *
106
+ * @param caller - Pass `this` from the calling component.
107
+ * @param tools - Array of tool definitions with Name, Description,
108
+ * ParameterSchema (JSON Schema), and Handler function.
109
+ */
110
+ SetAgentClientTools(caller: BaseResourceComponent, tools: Array<{
111
+ Name: string;
112
+ Description: string;
113
+ ParameterSchema: Record<string, unknown>;
114
+ Handler: (params: Record<string, unknown>) => Promise<unknown>;
115
+ }>): void;
50
116
  ngOnDestroy(): void;
51
117
  /**
52
118
  * Set up global keyboard event listeners to track shift key state
@@ -118,6 +184,17 @@ export declare class NavigationService implements OnDestroy {
118
184
  * @param options Navigation options including optional newRecordValues for pre-populating fields
119
185
  */
120
186
  OpenNewEntityRecord(entityName: string, options?: NavigationOptions): string;
187
+ /**
188
+ * Open a universal search results tab for the given query.
189
+ * This is the primary way to open search results from anywhere in the application.
190
+ *
191
+ * @param query The search query text
192
+ * @param searchOptions Optional search-specific options (e.g., minRelevance)
193
+ * @param options Navigation options
194
+ */
195
+ OpenSearch(query: string, searchOptions?: {
196
+ minRelevance?: number;
197
+ }, options?: NavigationOptions): string;
121
198
  /**
122
199
  * Navigate to a nav item by name within the current or specified application.
123
200
  * Allows passing additional configuration parameters to merge with the nav item's config.
@@ -157,6 +234,12 @@ export declare class NavigationService implements OnDestroy {
157
234
  * navigationService.UpdateActiveTabQueryParams({ category: null });
158
235
  */
159
236
  UpdateActiveTabQueryParams(queryParams: Record<string, string | null>): void;
237
+ /**
238
+ * Notify subscribers that query params changed on a specific tab.
239
+ * Called by the shell when back/forward navigation changes query params on the active tab.
240
+ * The notification includes the tab ID so only the component in that tab reacts.
241
+ */
242
+ NotifyQueryParamsChanged(tabId: string, params: Record<string, string>): void;
160
243
  /**
161
244
  * Apply query params to a specific tab by ID.
162
245
  * Merges with any existing query params on the tab. Use null values to remove params.
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.service.d.ts","sourceRoot":"","sources":["../../src/lib/navigation.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"navigation.service.d.ts","sourceRoot":"","sources":["../../src/lib/navigation.service.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Injectable } from '@angular/core';
2
- import { fromEvent } from 'rxjs';
2
+ import { fromEvent, Subject } from 'rxjs';
3
3
  import { UUIDsEqual } from '@memberjunction/global';
4
4
  import * as i0 from "@angular/core";
5
5
  import * as i1 from "@memberjunction/ng-base-application";
@@ -22,6 +22,9 @@ export class NavigationService {
22
22
  appManager;
23
23
  shiftKeyPressed = false;
24
24
  subscriptions = [];
25
+ queryParamChanged$ = new Subject();
26
+ /** Observable that emits when query params change on a tab (back/forward navigation). */
27
+ QueryParamChanged$ = this.queryParamChanged$.asObservable();
25
28
  /** Cached Home app ID (null means not found, undefined means not checked) */
26
29
  _homeAppId = undefined;
27
30
  /** Cached Home app color */
@@ -103,6 +106,42 @@ export class NavigationService {
103
106
  this._homeAppId = undefined;
104
107
  this._homeAppColor = null;
105
108
  }
109
+ // ════════════════════════════════════════════
110
+ // Agent Context & Client Tools
111
+ // ════════════════════════════════════════════
112
+ /**
113
+ * Observable stream of agent context updates from resource components.
114
+ * The shell subscribes to this to update the ComponentCacheManager and
115
+ * push changes to the chat overlay's AppContextSnapshot.DashboardContext.
116
+ */
117
+ AgentContextUpdated$ = new Subject();
118
+ /**
119
+ * Report the current agent-visible state from a resource component.
120
+ * Call this whenever the dashboard's internal state changes (tab switch,
121
+ * filter change, pipeline status change, drill-down, etc.).
122
+ *
123
+ * @param caller - Pass `this` from the calling component. Used to match
124
+ * against the ComponentCacheManager to identify which cached component
125
+ * this update belongs to.
126
+ * @param context - Key-value pairs representing dashboard state the agent
127
+ * should know about. Each dashboard defines its own shape.
128
+ */
129
+ SetAgentContext(caller, context) {
130
+ this.AgentContextUpdated$.next({ Caller: caller, AgentContext: context });
131
+ }
132
+ /**
133
+ * Register the client tools available from a resource component.
134
+ * Call this on component init and whenever the available tools change.
135
+ * Tools are automatically unregistered when the component becomes inactive
136
+ * (tab switch) and re-registered when it becomes active again.
137
+ *
138
+ * @param caller - Pass `this` from the calling component.
139
+ * @param tools - Array of tool definitions with Name, Description,
140
+ * ParameterSchema (JSON Schema), and Handler function.
141
+ */
142
+ SetAgentClientTools(caller, tools) {
143
+ this.AgentContextUpdated$.next({ Caller: caller, AgentClientTools: tools });
144
+ }
106
145
  ngOnDestroy() {
107
146
  this.subscriptions.forEach(sub => sub.unsubscribe());
108
147
  }
@@ -466,6 +505,43 @@ export class NavigationService {
466
505
  return this.workspaceManager.OpenTab(request, appColor);
467
506
  }
468
507
  }
508
+ /**
509
+ * Open a universal search results tab for the given query.
510
+ * This is the primary way to open search results from anywhere in the application.
511
+ *
512
+ * @param query The search query text
513
+ * @param searchOptions Optional search-specific options (e.g., minRelevance)
514
+ * @param options Navigation options
515
+ */
516
+ OpenSearch(query, searchOptions, options) {
517
+ const appId = this.getDefaultApplicationId();
518
+ const appColor = this.getDefaultAppColor();
519
+ const forceNew = this.shouldForceNewTab(options);
520
+ const config = {
521
+ resourceType: 'Search Results',
522
+ Query: query,
523
+ SearchInput: query,
524
+ recordId: `search-${query}`
525
+ };
526
+ if (searchOptions?.minRelevance != null) {
527
+ config['MinRelevance'] = searchOptions.minRelevance;
528
+ }
529
+ const request = {
530
+ ApplicationId: appId,
531
+ Title: `Search: ${query}`,
532
+ Configuration: config,
533
+ ResourceRecordId: `search-${query}`,
534
+ IsPinned: false
535
+ };
536
+ // Handle transition from single-resource mode
537
+ this.handleSingleResourceModeTransition(forceNew, request);
538
+ if (forceNew) {
539
+ return this.workspaceManager.OpenTabForced(request, appColor);
540
+ }
541
+ else {
542
+ return this.workspaceManager.OpenTab(request, appColor);
543
+ }
544
+ }
469
545
  /**
470
546
  * Navigate to a nav item by name within the current or specified application.
471
547
  * Allows passing additional configuration parameters to merge with the nav item's config.
@@ -582,6 +658,14 @@ export class NavigationService {
582
658
  }
583
659
  this.applyQueryParamsToTab(activeTabId, queryParams);
584
660
  }
661
+ /**
662
+ * Notify subscribers that query params changed on a specific tab.
663
+ * Called by the shell when back/forward navigation changes query params on the active tab.
664
+ * The notification includes the tab ID so only the component in that tab reacts.
665
+ */
666
+ NotifyQueryParamsChanged(tabId, params) {
667
+ this.queryParamChanged$.next({ TabId: tabId, Params: params });
668
+ }
585
669
  /**
586
670
  * Apply query params to a specific tab by ID.
587
671
  * Merges with any existing query params on the tab. Use null values to remove params.
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.service.js","sourceRoot":"","sources":["../../src/lib/navigation.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy } from '@angular/core';\nimport { WorkspaceStateManager, NavItem, DynamicNavItem, TabRequest, ApplicationManager } from '@memberjunction/ng-base-application';\nimport { NavigationOptions } from './navigation.interfaces';\nimport { CompositeKey } from '@memberjunction/core';\nimport { fromEvent, Subscription } from 'rxjs';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * System application ID for non-app-specific resources (fallback only)\n * Uses double underscore prefix to indicate system-level resource\n * @deprecated Prefer using NavigationService.getDefaultApplicationId() instead\n */\nexport const SYSTEM_APP_ID = '__explorer';\n\n/**\n * Neutral color for fallback when no app is available\n */\nconst NEUTRAL_APP_COLOR = '#9E9E9E'; // Material Design Gray 500\n\n/**\n * Centralized navigation service that handles all navigation operations\n * with automatic shift-key detection for power user workflows\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class NavigationService implements OnDestroy {\n private shiftKeyPressed = false;\n private subscriptions: Subscription[] = [];\n\n /** Cached Home app ID (null means not found, undefined means not checked) */\n private _homeAppId: string | null | undefined = undefined;\n /** Cached Home app color */\n private _homeAppColor: string | null = null;\n\n constructor(\n private workspaceManager: WorkspaceStateManager,\n private appManager: ApplicationManager\n ) {\n this.setupGlobalShiftKeyDetection();\n }\n\n /**\n * Get the neutral color used for system-wide resources (entities, views, dashboards)\n * Returns a light neutral gray\n * @deprecated Use getDefaultAppColor() for better UX with Home app integration\n */\n get ExplorerAppColor(): string {\n return NEUTRAL_APP_COLOR;\n }\n\n /**\n * Gets the default application ID for orphan resources.\n * Priority: Home app > Active app > SYSTEM_APP_ID\n *\n * This ensures orphan resources (entity records, dashboards, views opened directly)\n * are grouped under the Home app instead of being orphaned in the tab system.\n */\n private getDefaultApplicationId(): string {\n // Check cache first\n if (this._homeAppId !== undefined) {\n if (this._homeAppId !== null) {\n return this._homeAppId;\n }\n // Home app not found, check active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.ID;\n }\n return SYSTEM_APP_ID;\n }\n\n // First time - look for Home app\n const homeApp = this.appManager.GetAppByName('Home');\n if (homeApp) {\n this._homeAppId = homeApp.ID;\n this._homeAppColor = homeApp.GetColor();\n return homeApp.ID;\n }\n\n // Cache that Home app doesn't exist\n this._homeAppId = null;\n\n // Fall back to currently active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.ID;\n }\n\n // Last resort - system app ID\n return SYSTEM_APP_ID;\n }\n\n /**\n * Gets the default app color for orphan resources.\n * Returns Home app color if available, otherwise neutral gray.\n */\n private getDefaultAppColor(): string {\n // Ensure cache is populated\n this.getDefaultApplicationId();\n\n // If Home app exists, use its color\n if (this._homeAppColor) {\n return this._homeAppColor;\n }\n\n // Check active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.GetColor();\n }\n\n // Fall back to neutral color\n return NEUTRAL_APP_COLOR;\n }\n\n /**\n * Clears the cached Home app info.\n * Call this if apps are reloaded or user logs out.\n */\n public clearHomeAppCache(): void {\n this._homeAppId = undefined;\n this._homeAppColor = null;\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach(sub => sub.unsubscribe());\n }\n\n /**\n * Set up global keyboard event listeners to track shift key state\n */\n private setupGlobalShiftKeyDetection(): void {\n // Track shift key via mousedown events (capture phase) instead of keydown/keyup.\n // This is more reliable because:\n // 1. MouseEvent.shiftKey always reflects the actual modifier state at click time\n // 2. No risk of \"stuck\" state from missed keyup events (focus loss, tab switch, etc.)\n // 3. Navigation is always triggered by a click, so the shift state is read\n // at exactly the right moment\n this.subscriptions.push(\n fromEvent<MouseEvent>(document, 'mousedown', { capture: true }).subscribe(event => {\n this.shiftKeyPressed = event.shiftKey;\n })\n );\n }\n\n /**\n * Get current shift key state\n */\n private isShiftPressed(): boolean {\n return this.shiftKeyPressed;\n }\n\n /**\n * Determine if a new tab should be forced based on options and shift key state\n */\n private shouldForceNewTab(options?: NavigationOptions): boolean {\n // If forceNewTab is explicitly set, use that\n if (options?.forceNewTab !== undefined) {\n return options.forceNewTab;\n }\n\n // Otherwise, use global shift key detection\n return this.isShiftPressed();\n }\n\n /**\n * Handle temporary tab preservation when forcing new tabs\n * Rule: Only ONE tab should be temporary at a time\n * When shift+clicking to force a new tab, pin the current active tab if it's temporary\n */\n private handleSingleResourceModeTransition(forceNew: boolean, newRequest: TabRequest): void {\n if (!forceNew) {\n return; // Normal navigation, not forcing new tab\n }\n\n const config = this.workspaceManager.GetConfiguration();\n\n if (!config || !config.tabs || config.tabs.length === 0) {\n return; // No tabs to preserve\n }\n\n // Find the currently active tab\n const activeTab = config.tabs.find(tab => tab.id === config.activeTabId);\n if (!activeTab) {\n return; // No active tab\n }\n\n // If the active tab is NOT pinned (i.e., it's temporary), pin it to preserve it\n // This maintains the \"only one temporary tab\" rule\n if (!activeTab.isPinned) {\n this.workspaceManager.TogglePin(activeTab.id);\n }\n }\n\n /**\n * Check if a tab request matches an existing tab's resource\n */\n private isSameResource(tab: any, request: TabRequest): boolean {\n // Different apps = different resources\n if (tab.applicationId !== request.ApplicationId) {\n return false;\n }\n\n // For resource-based tabs, compare resourceType and recordId\n if (request.Configuration?.resourceType) {\n const requestRecordId = request.ResourceRecordId || '';\n const tabRecordId = tab.resourceRecordId || '';\n return tab.configuration?.resourceType === request.Configuration.resourceType &&\n tabRecordId === requestRecordId;\n }\n\n // For app nav items, compare appName and navItemName\n if (request.Configuration?.appName && request.Configuration?.navItemName) {\n return tab.configuration?.appName === request.Configuration.appName &&\n tab.configuration?.navItemName === request.Configuration.navItemName;\n }\n\n // Fallback to basic comparison\n return false;\n }\n\n /**\n * Open a navigation item within an app\n */\n public OpenNavItem(appId: string, navItem: NavItem, appColor: string, options?: NavigationOptions): string {\n const forceNew = this.shouldForceNewTab(options);\n\n // Get the app to find its name\n const app = this.appManager.GetAppById(appId);\n const appName = app?.Name || '';\n\n // Dynamic nav items (e.g. orphan entity records) carry their original tab Configuration\n // and should NOT get navItemName stamped on them — that would cause buildResourceUrl\n // to produce a nav-item-style URL like /app/home/<label> instead of the correct\n // resource-type URL like /app/home/record/Entity/ID|...\n const isDynamic = (navItem as DynamicNavItem).isDynamic === true;\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: navItem.Label,\n ResourceRecordId: navItem.RecordID || '', // Also store at top level for consistent tab matching\n Configuration: {\n route: navItem.Route,\n resourceType: navItem.ResourceType,\n driverClass: navItem.DriverClass, // Pass through DriverClass for Custom resource type\n recordId: navItem.RecordID,\n appName: appName, // Store app name for URL building\n appId: appId,\n ...(isDynamic ? {} : { navItemName: navItem.Label }), // Only set for static nav items\n ...(navItem.Configuration || {})\n },\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n let tabId: string;\n if (forceNew) {\n // Always create a new tab\n tabId = this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n // Use existing OpenTab logic (may replace temporary tab)\n tabId = this.workspaceManager.OpenTab(request, appColor);\n }\n\n // Apply query params to the newly opened/activated tab if provided\n if (options?.queryParams) {\n this.applyQueryParamsToTab(tabId, options.queryParams);\n }\n\n return tabId;\n }\n\n /**\n * Open an entity record view\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenEntityRecord(\n entityName: string,\n recordPkey: CompositeKey,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n\n const forceNew = this.shouldForceNewTab(options);\n\n const recordId = recordPkey.ToURLSegment();\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `${entityName} - ${recordId}`,\n Configuration: {\n resourceType: 'Records',\n Entity: entityName, // Must use 'Entity' (capital E) - expected by record-resource.component\n recordId: recordId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: recordId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n let tabId: string;\n if (forceNew) {\n tabId = this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n tabId = this.workspaceManager.OpenTab(request, appColor);\n }\n\n return tabId;\n }\n\n /**\n * Open a view\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenView(\n viewId: string,\n viewName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: viewName,\n Configuration: {\n resourceType: 'MJ: User Views',\n viewId,\n recordId: viewId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: viewId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a dashboard\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenDashboard(\n dashboardId: string,\n dashboardName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: dashboardName,\n Configuration: {\n resourceType: 'Dashboards',\n dashboardId,\n recordId: dashboardId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: dashboardId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a report\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenReport(\n reportId: string,\n reportName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: reportName,\n Configuration: {\n resourceType: 'Reports',\n reportId,\n recordId: reportId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: reportId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open an artifact\n * Artifacts are versioned content containers (reports, dashboards, UI components, etc.)\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenArtifact(\n artifactId: string,\n artifactName?: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: artifactName || `Artifact - ${artifactId}`,\n Configuration: {\n resourceType: 'Artifacts',\n artifactId,\n recordId: artifactId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: artifactId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a dynamic view\n * Dynamic views are entity-based views with custom filters, not saved views\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenDynamicView(\n entityName: string,\n extraFilter?: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const filterSuffix = extraFilter ? ' (Filtered)' : '';\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `${entityName}${filterSuffix}`,\n Configuration: {\n resourceType: 'MJ: User Views',\n Entity: entityName,\n ExtraFilter: extraFilter,\n isDynamic: true,\n recordId: 'dynamic' // Special marker for dynamic views\n },\n ResourceRecordId: 'dynamic',\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a query\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenQuery(\n queryId: string,\n queryName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: queryName,\n Configuration: {\n resourceType: 'Queries',\n queryId,\n recordId: queryId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: queryId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a new entity record creation form\n * Uses Home app if available, otherwise falls back to active app or system app\n * @param entityName The name of the entity to create a new record for\n * @param options Navigation options including optional newRecordValues for pre-populating fields\n */\n public OpenNewEntityRecord(\n entityName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `New ${entityName}`,\n Configuration: {\n resourceType: 'Records',\n Entity: entityName, // Must use 'Entity' (capital E) - expected by record-resource.component\n recordId: '', // Empty recordId indicates new record\n isNew: true, // Flag to indicate this is a new record\n NewRecordValues: options?.newRecordValues // Pass through initial values if provided\n },\n ResourceRecordId: '', // Empty for new records\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Navigate to a nav item by name within the current or specified application.\n * Allows passing additional configuration parameters to merge with the nav item's config.\n * This is useful for cross-resource navigation where a component needs to navigate\n * to another nav item with specific parameters (e.g., navigate to Conversations with a specific conversationId).\n *\n * @param navItemName The label/name of the nav item to navigate to\n * @param configuration Additional configuration to merge (e.g., conversationId, artifactId)\n * @param appId Optional app ID (defaults to current active app)\n * @param options Navigation options\n * @returns The tab ID if successful, null if nav item not found\n */\n public async OpenNavItemByName(\n navItemName: string,\n configuration?: Record<string, unknown>,\n appId?: string,\n options?: NavigationOptions\n ): Promise<string | null> {\n // Get app (use provided or current active)\n const targetAppId = appId || this.appManager.GetActiveApp()?.ID;\n if (!targetAppId) {\n return null;\n }\n\n const app = this.appManager.GetAppById(targetAppId);\n if (!app) {\n return null;\n }\n\n // Find the nav item by name\n const navItems = await app.GetNavItems();\n const navItem = navItems.find(item => item.Label === navItemName);\n if (!navItem) {\n return null;\n }\n\n // Create a merged nav item with additional configuration\n const mergedNavItem: NavItem = {\n ...navItem,\n Configuration: {\n ...(navItem.Configuration || {}),\n ...(configuration || {})\n }\n };\n\n // Use existing OpenNavItem\n return this.OpenNavItem(targetAppId, mergedNavItem, app.GetColor(), options);\n }\n\n /**\n * Switch to an application by ID.\n * This sets the app as active and either opens a specific nav item or creates a default tab.\n * If the requested nav item already has an open tab, switches to that tab instead of creating a new one.\n * @param appId The application ID to switch to\n * @param navItemName Optional name of a nav item to open within the app. If provided, opens that nav item.\n */\n async SwitchToApp(appId: string, navItemName?: string): Promise<void> {\n await this.appManager.SetActiveApp(appId);\n\n const app = this.appManager.GetAllApps().find(a => UUIDsEqual(a.ID, appId));\n if (!app) {\n return;\n }\n\n const appTabs = this.workspaceManager.GetAppTabs(appId);\n\n // If a specific nav item is requested\n if (navItemName) {\n const navItems = await app.GetNavItems();\n const navItem = navItems.find(item => item.Label === navItemName);\n if (navItem) {\n // Check if there's already a tab for this nav item\n const existingTab = appTabs.find(tab =>\n tab.title === navItem.Label ||\n (tab.configuration?.['route'] === navItem.Route && navItem.Route)\n );\n\n if (existingTab) {\n // Switch to existing tab\n this.workspaceManager.SetActiveTab(existingTab.id);\n } else {\n // Open new tab for this nav item\n this.OpenNavItem(appId, navItem, app.GetColor());\n }\n return;\n }\n // Nav item not found, fall through to default behavior\n }\n\n // No specific nav item requested - check if app has any tabs\n if (appTabs.length === 0) {\n // Create default tab\n const tabRequest = await app.CreateDefaultTab();\n if (tabRequest) {\n this.workspaceManager.OpenTab(tabRequest, app.GetColor());\n }\n } else {\n // App has tabs - switch to the first one (or active one if exists)\n const config = this.workspaceManager.GetConfiguration();\n const activeAppTab = appTabs.find(t => t.id === config?.activeTabId);\n if (!activeAppTab) {\n // No active tab for this app, switch to first tab\n this.workspaceManager.SetActiveTab(appTabs[0].id);\n }\n }\n }\n\n /**\n * Update the query params for the currently active tab.\n * This updates the tab's configuration and triggers a URL sync via the shell's\n * workspace configuration subscription.\n *\n * Use this instead of directly calling router.navigate() to ensure proper\n * URL management that respects app-scoped routes.\n *\n * @param queryParams Object containing query param key-value pairs.\n * Use null values to remove a query param.\n * @example\n * // Add or update query params\n * navigationService.UpdateActiveTabQueryParams({ category: 'abc123', dashboard: 'xyz789' });\n *\n * // Remove a query param\n * navigationService.UpdateActiveTabQueryParams({ category: null });\n */\n UpdateActiveTabQueryParams(queryParams: Record<string, string | null>): void {\n const activeTabId = this.workspaceManager.GetActiveTabId();\n if (!activeTabId) {\n console.warn('NavigationService.UpdateActiveTabQueryParams: No active tab');\n return;\n }\n\n this.applyQueryParamsToTab(activeTabId, queryParams);\n }\n\n /**\n * Apply query params to a specific tab by ID.\n * Merges with any existing query params on the tab. Use null values to remove params.\n */\n private applyQueryParamsToTab(tabId: string, queryParams: Record<string, string | null>): void {\n const tab = this.workspaceManager.GetTab(tabId);\n if (!tab) {\n console.warn('NavigationService.applyQueryParamsToTab: Tab not found:', tabId);\n return;\n }\n\n // Get existing queryParams from tab configuration\n const existingQueryParams = (tab.configuration?.['queryParams'] || {}) as Record<string, string | null>;\n\n // Merge with new query params\n const mergedQueryParams: Record<string, string> = {};\n\n // Start with existing params (excluding nulls)\n for (const [key, value] of Object.entries(existingQueryParams)) {\n if (value !== null) {\n mergedQueryParams[key] = value;\n }\n }\n\n // Apply new params (null means remove)\n for (const [key, value] of Object.entries(queryParams)) {\n if (value === null) {\n delete mergedQueryParams[key];\n } else {\n mergedQueryParams[key] = value;\n }\n }\n\n // Update the tab configuration\n this.workspaceManager.UpdateTabConfiguration(tabId, {\n queryParams: Object.keys(mergedQueryParams).length > 0 ? mergedQueryParams : undefined\n });\n }\n}\n"]}
1
+ {"version":3,"file":"navigation.service.js","sourceRoot":"","sources":["../../src/lib/navigation.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy } from '@angular/core';\nimport { WorkspaceStateManager, NavItem, DynamicNavItem, TabRequest, ApplicationManager } from '@memberjunction/ng-base-application';\nimport { NavigationOptions } from './navigation.interfaces';\nimport { CompositeKey } from '@memberjunction/core';\nimport { fromEvent, Subject, Subscription } from 'rxjs';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { BaseResourceComponent } from './base-resource-component';\n\n/**\n * Event emitted when query params change on a tab (e.g., from browser back/forward).\n * Includes the tab ID so that only the component in the affected tab reacts,\n * preventing cross-tab leakage in multi-tab scenarios.\n */\nexport interface QueryParamChangeEvent {\n TabId: string;\n Params: Record<string, string>;\n}\n\n/**\n * Event emitted when a resource component reports its agent context or tools.\n * The shell (which owns the ComponentCacheManager) subscribes to these events\n * and updates the cache + active AppContextSnapshot accordingly.\n */\nexport interface AgentContextUpdate {\n /** The component instance that reported the update */\n Caller: BaseResourceComponent;\n /** Dashboard-specific context for the agent (undefined = no change) */\n AgentContext?: Record<string, unknown>;\n /** Client tools available from this dashboard (undefined = no change) */\n AgentClientTools?: Array<{\n Name: string;\n Description: string;\n ParameterSchema: Record<string, unknown>;\n Handler: (params: Record<string, unknown>) => Promise<unknown>;\n }>;\n}\n\n/**\n * System application ID for non-app-specific resources (fallback only)\n * Uses double underscore prefix to indicate system-level resource\n * @deprecated Prefer using NavigationService.getDefaultApplicationId() instead\n */\nexport const SYSTEM_APP_ID = '__explorer';\n\n/**\n * Neutral color for fallback when no app is available\n */\nconst NEUTRAL_APP_COLOR = '#9E9E9E'; // Material Design Gray 500\n\n/**\n * Centralized navigation service that handles all navigation operations\n * with automatic shift-key detection for power user workflows\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class NavigationService implements OnDestroy {\n private shiftKeyPressed = false;\n private subscriptions: Subscription[] = [];\n\n private queryParamChanged$ = new Subject<QueryParamChangeEvent>();\n /** Observable that emits when query params change on a tab (back/forward navigation). */\n public QueryParamChanged$ = this.queryParamChanged$.asObservable();\n\n /** Cached Home app ID (null means not found, undefined means not checked) */\n private _homeAppId: string | null | undefined = undefined;\n /** Cached Home app color */\n private _homeAppColor: string | null = null;\n\n constructor(\n private workspaceManager: WorkspaceStateManager,\n private appManager: ApplicationManager\n ) {\n this.setupGlobalShiftKeyDetection();\n }\n\n /**\n * Get the neutral color used for system-wide resources (entities, views, dashboards)\n * Returns a light neutral gray\n * @deprecated Use getDefaultAppColor() for better UX with Home app integration\n */\n get ExplorerAppColor(): string {\n return NEUTRAL_APP_COLOR;\n }\n\n /**\n * Gets the default application ID for orphan resources.\n * Priority: Home app > Active app > SYSTEM_APP_ID\n *\n * This ensures orphan resources (entity records, dashboards, views opened directly)\n * are grouped under the Home app instead of being orphaned in the tab system.\n */\n private getDefaultApplicationId(): string {\n // Check cache first\n if (this._homeAppId !== undefined) {\n if (this._homeAppId !== null) {\n return this._homeAppId;\n }\n // Home app not found, check active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.ID;\n }\n return SYSTEM_APP_ID;\n }\n\n // First time - look for Home app\n const homeApp = this.appManager.GetAppByName('Home');\n if (homeApp) {\n this._homeAppId = homeApp.ID;\n this._homeAppColor = homeApp.GetColor();\n return homeApp.ID;\n }\n\n // Cache that Home app doesn't exist\n this._homeAppId = null;\n\n // Fall back to currently active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.ID;\n }\n\n // Last resort - system app ID\n return SYSTEM_APP_ID;\n }\n\n /**\n * Gets the default app color for orphan resources.\n * Returns Home app color if available, otherwise neutral gray.\n */\n private getDefaultAppColor(): string {\n // Ensure cache is populated\n this.getDefaultApplicationId();\n\n // If Home app exists, use its color\n if (this._homeAppColor) {\n return this._homeAppColor;\n }\n\n // Check active app\n const activeApp = this.appManager.GetActiveApp();\n if (activeApp) {\n return activeApp.GetColor();\n }\n\n // Fall back to neutral color\n return NEUTRAL_APP_COLOR;\n }\n\n /**\n * Clears the cached Home app info.\n * Call this if apps are reloaded or user logs out.\n */\n public clearHomeAppCache(): void {\n this._homeAppId = undefined;\n this._homeAppColor = null;\n }\n\n // ════════════════════════════════════════════\n // Agent Context & Client Tools\n // ════════════════════════════════════════════\n\n /**\n * Observable stream of agent context updates from resource components.\n * The shell subscribes to this to update the ComponentCacheManager and\n * push changes to the chat overlay's AppContextSnapshot.DashboardContext.\n */\n public readonly AgentContextUpdated$ = new Subject<AgentContextUpdate>();\n\n /**\n * Report the current agent-visible state from a resource component.\n * Call this whenever the dashboard's internal state changes (tab switch,\n * filter change, pipeline status change, drill-down, etc.).\n *\n * @param caller - Pass `this` from the calling component. Used to match\n * against the ComponentCacheManager to identify which cached component\n * this update belongs to.\n * @param context - Key-value pairs representing dashboard state the agent\n * should know about. Each dashboard defines its own shape.\n */\n public SetAgentContext(caller: BaseResourceComponent, context: Record<string, unknown>): void {\n this.AgentContextUpdated$.next({ Caller: caller, AgentContext: context });\n }\n\n /**\n * Register the client tools available from a resource component.\n * Call this on component init and whenever the available tools change.\n * Tools are automatically unregistered when the component becomes inactive\n * (tab switch) and re-registered when it becomes active again.\n *\n * @param caller - Pass `this` from the calling component.\n * @param tools - Array of tool definitions with Name, Description,\n * ParameterSchema (JSON Schema), and Handler function.\n */\n public SetAgentClientTools(caller: BaseResourceComponent, tools: Array<{\n Name: string;\n Description: string;\n ParameterSchema: Record<string, unknown>;\n Handler: (params: Record<string, unknown>) => Promise<unknown>;\n }>): void {\n this.AgentContextUpdated$.next({ Caller: caller, AgentClientTools: tools });\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach(sub => sub.unsubscribe());\n }\n\n /**\n * Set up global keyboard event listeners to track shift key state\n */\n private setupGlobalShiftKeyDetection(): void {\n // Track shift key via mousedown events (capture phase) instead of keydown/keyup.\n // This is more reliable because:\n // 1. MouseEvent.shiftKey always reflects the actual modifier state at click time\n // 2. No risk of \"stuck\" state from missed keyup events (focus loss, tab switch, etc.)\n // 3. Navigation is always triggered by a click, so the shift state is read\n // at exactly the right moment\n this.subscriptions.push(\n fromEvent<MouseEvent>(document, 'mousedown', { capture: true }).subscribe(event => {\n this.shiftKeyPressed = event.shiftKey;\n })\n );\n }\n\n /**\n * Get current shift key state\n */\n private isShiftPressed(): boolean {\n return this.shiftKeyPressed;\n }\n\n /**\n * Determine if a new tab should be forced based on options and shift key state\n */\n private shouldForceNewTab(options?: NavigationOptions): boolean {\n // If forceNewTab is explicitly set, use that\n if (options?.forceNewTab !== undefined) {\n return options.forceNewTab;\n }\n\n // Otherwise, use global shift key detection\n return this.isShiftPressed();\n }\n\n /**\n * Handle temporary tab preservation when forcing new tabs\n * Rule: Only ONE tab should be temporary at a time\n * When shift+clicking to force a new tab, pin the current active tab if it's temporary\n */\n private handleSingleResourceModeTransition(forceNew: boolean, newRequest: TabRequest): void {\n if (!forceNew) {\n return; // Normal navigation, not forcing new tab\n }\n\n const config = this.workspaceManager.GetConfiguration();\n\n if (!config || !config.tabs || config.tabs.length === 0) {\n return; // No tabs to preserve\n }\n\n // Find the currently active tab\n const activeTab = config.tabs.find(tab => tab.id === config.activeTabId);\n if (!activeTab) {\n return; // No active tab\n }\n\n // If the active tab is NOT pinned (i.e., it's temporary), pin it to preserve it\n // This maintains the \"only one temporary tab\" rule\n if (!activeTab.isPinned) {\n this.workspaceManager.TogglePin(activeTab.id);\n }\n }\n\n /**\n * Check if a tab request matches an existing tab's resource\n */\n private isSameResource(tab: any, request: TabRequest): boolean {\n // Different apps = different resources\n if (tab.applicationId !== request.ApplicationId) {\n return false;\n }\n\n // For resource-based tabs, compare resourceType and recordId\n if (request.Configuration?.resourceType) {\n const requestRecordId = request.ResourceRecordId || '';\n const tabRecordId = tab.resourceRecordId || '';\n return tab.configuration?.resourceType === request.Configuration.resourceType &&\n tabRecordId === requestRecordId;\n }\n\n // For app nav items, compare appName and navItemName\n if (request.Configuration?.appName && request.Configuration?.navItemName) {\n return tab.configuration?.appName === request.Configuration.appName &&\n tab.configuration?.navItemName === request.Configuration.navItemName;\n }\n\n // Fallback to basic comparison\n return false;\n }\n\n /**\n * Open a navigation item within an app\n */\n public OpenNavItem(appId: string, navItem: NavItem, appColor: string, options?: NavigationOptions): string {\n const forceNew = this.shouldForceNewTab(options);\n\n // Get the app to find its name\n const app = this.appManager.GetAppById(appId);\n const appName = app?.Name || '';\n\n // Dynamic nav items (e.g. orphan entity records) carry their original tab Configuration\n // and should NOT get navItemName stamped on them — that would cause buildResourceUrl\n // to produce a nav-item-style URL like /app/home/<label> instead of the correct\n // resource-type URL like /app/home/record/Entity/ID|...\n const isDynamic = (navItem as DynamicNavItem).isDynamic === true;\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: navItem.Label,\n ResourceRecordId: navItem.RecordID || '', // Also store at top level for consistent tab matching\n Configuration: {\n route: navItem.Route,\n resourceType: navItem.ResourceType,\n driverClass: navItem.DriverClass, // Pass through DriverClass for Custom resource type\n recordId: navItem.RecordID,\n appName: appName, // Store app name for URL building\n appId: appId,\n ...(isDynamic ? {} : { navItemName: navItem.Label }), // Only set for static nav items\n ...(navItem.Configuration || {})\n },\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n let tabId: string;\n if (forceNew) {\n // Always create a new tab\n tabId = this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n // Use existing OpenTab logic (may replace temporary tab)\n tabId = this.workspaceManager.OpenTab(request, appColor);\n }\n\n // Apply query params to the newly opened/activated tab if provided\n if (options?.queryParams) {\n this.applyQueryParamsToTab(tabId, options.queryParams);\n }\n\n return tabId;\n }\n\n /**\n * Open an entity record view\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenEntityRecord(\n entityName: string,\n recordPkey: CompositeKey,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n\n const forceNew = this.shouldForceNewTab(options);\n\n const recordId = recordPkey.ToURLSegment();\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `${entityName} - ${recordId}`,\n Configuration: {\n resourceType: 'Records',\n Entity: entityName, // Must use 'Entity' (capital E) - expected by record-resource.component\n recordId: recordId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: recordId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n let tabId: string;\n if (forceNew) {\n tabId = this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n tabId = this.workspaceManager.OpenTab(request, appColor);\n }\n\n return tabId;\n }\n\n /**\n * Open a view\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenView(\n viewId: string,\n viewName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: viewName,\n Configuration: {\n resourceType: 'MJ: User Views',\n viewId,\n recordId: viewId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: viewId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a dashboard\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenDashboard(\n dashboardId: string,\n dashboardName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: dashboardName,\n Configuration: {\n resourceType: 'Dashboards',\n dashboardId,\n recordId: dashboardId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: dashboardId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a report\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenReport(\n reportId: string,\n reportName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: reportName,\n Configuration: {\n resourceType: 'Reports',\n reportId,\n recordId: reportId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: reportId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open an artifact\n * Artifacts are versioned content containers (reports, dashboards, UI components, etc.)\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenArtifact(\n artifactId: string,\n artifactName?: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: artifactName || `Artifact - ${artifactId}`,\n Configuration: {\n resourceType: 'Artifacts',\n artifactId,\n recordId: artifactId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: artifactId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a dynamic view\n * Dynamic views are entity-based views with custom filters, not saved views\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenDynamicView(\n entityName: string,\n extraFilter?: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const filterSuffix = extraFilter ? ' (Filtered)' : '';\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `${entityName}${filterSuffix}`,\n Configuration: {\n resourceType: 'MJ: User Views',\n Entity: entityName,\n ExtraFilter: extraFilter,\n isDynamic: true,\n recordId: 'dynamic' // Special marker for dynamic views\n },\n ResourceRecordId: 'dynamic',\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a query\n * Uses Home app if available, otherwise falls back to active app or system app\n */\n public OpenQuery(\n queryId: string,\n queryName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: queryName,\n Configuration: {\n resourceType: 'Queries',\n queryId,\n recordId: queryId // Also needed in Configuration for tab-container.component to populate ResourceRecordID\n },\n ResourceRecordId: queryId,\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a new entity record creation form\n * Uses Home app if available, otherwise falls back to active app or system app\n * @param entityName The name of the entity to create a new record for\n * @param options Navigation options including optional newRecordValues for pre-populating fields\n */\n public OpenNewEntityRecord(\n entityName: string,\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n\n const forceNew = this.shouldForceNewTab(options);\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `New ${entityName}`,\n Configuration: {\n resourceType: 'Records',\n Entity: entityName, // Must use 'Entity' (capital E) - expected by record-resource.component\n recordId: '', // Empty recordId indicates new record\n isNew: true, // Flag to indicate this is a new record\n NewRecordValues: options?.newRecordValues // Pass through initial values if provided\n },\n ResourceRecordId: '', // Empty for new records\n IsPinned: options?.pinTab || false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Open a universal search results tab for the given query.\n * This is the primary way to open search results from anywhere in the application.\n *\n * @param query The search query text\n * @param searchOptions Optional search-specific options (e.g., minRelevance)\n * @param options Navigation options\n */\n public OpenSearch(\n query: string,\n searchOptions?: { minRelevance?: number },\n options?: NavigationOptions\n ): string {\n const appId = this.getDefaultApplicationId();\n const appColor = this.getDefaultAppColor();\n const forceNew = this.shouldForceNewTab(options);\n\n const config: Record<string, unknown> = {\n resourceType: 'Search Results',\n Query: query,\n SearchInput: query,\n recordId: `search-${query}`\n };\n if (searchOptions?.minRelevance != null) {\n config['MinRelevance'] = searchOptions.minRelevance;\n }\n\n const request: TabRequest = {\n ApplicationId: appId,\n Title: `Search: ${query}`,\n Configuration: config,\n ResourceRecordId: `search-${query}`,\n IsPinned: false\n };\n\n // Handle transition from single-resource mode\n this.handleSingleResourceModeTransition(forceNew, request);\n\n if (forceNew) {\n return this.workspaceManager.OpenTabForced(request, appColor);\n } else {\n return this.workspaceManager.OpenTab(request, appColor);\n }\n }\n\n /**\n * Navigate to a nav item by name within the current or specified application.\n * Allows passing additional configuration parameters to merge with the nav item's config.\n * This is useful for cross-resource navigation where a component needs to navigate\n * to another nav item with specific parameters (e.g., navigate to Conversations with a specific conversationId).\n *\n * @param navItemName The label/name of the nav item to navigate to\n * @param configuration Additional configuration to merge (e.g., conversationId, artifactId)\n * @param appId Optional app ID (defaults to current active app)\n * @param options Navigation options\n * @returns The tab ID if successful, null if nav item not found\n */\n public async OpenNavItemByName(\n navItemName: string,\n configuration?: Record<string, unknown>,\n appId?: string,\n options?: NavigationOptions\n ): Promise<string | null> {\n // Get app (use provided or current active)\n const targetAppId = appId || this.appManager.GetActiveApp()?.ID;\n if (!targetAppId) {\n return null;\n }\n\n const app = this.appManager.GetAppById(targetAppId);\n if (!app) {\n return null;\n }\n\n // Find the nav item by name\n const navItems = await app.GetNavItems();\n const navItem = navItems.find(item => item.Label === navItemName);\n if (!navItem) {\n return null;\n }\n\n // Create a merged nav item with additional configuration\n const mergedNavItem: NavItem = {\n ...navItem,\n Configuration: {\n ...(navItem.Configuration || {}),\n ...(configuration || {})\n }\n };\n\n // Use existing OpenNavItem\n return this.OpenNavItem(targetAppId, mergedNavItem, app.GetColor(), options);\n }\n\n /**\n * Switch to an application by ID.\n * This sets the app as active and either opens a specific nav item or creates a default tab.\n * If the requested nav item already has an open tab, switches to that tab instead of creating a new one.\n * @param appId The application ID to switch to\n * @param navItemName Optional name of a nav item to open within the app. If provided, opens that nav item.\n */\n async SwitchToApp(appId: string, navItemName?: string): Promise<void> {\n await this.appManager.SetActiveApp(appId);\n\n const app = this.appManager.GetAllApps().find(a => UUIDsEqual(a.ID, appId));\n if (!app) {\n return;\n }\n\n const appTabs = this.workspaceManager.GetAppTabs(appId);\n\n // If a specific nav item is requested\n if (navItemName) {\n const navItems = await app.GetNavItems();\n const navItem = navItems.find(item => item.Label === navItemName);\n if (navItem) {\n // Check if there's already a tab for this nav item\n const existingTab = appTabs.find(tab =>\n tab.title === navItem.Label ||\n (tab.configuration?.['route'] === navItem.Route && navItem.Route)\n );\n\n if (existingTab) {\n // Switch to existing tab\n this.workspaceManager.SetActiveTab(existingTab.id);\n } else {\n // Open new tab for this nav item\n this.OpenNavItem(appId, navItem, app.GetColor());\n }\n return;\n }\n // Nav item not found, fall through to default behavior\n }\n\n // No specific nav item requested - check if app has any tabs\n if (appTabs.length === 0) {\n // Create default tab\n const tabRequest = await app.CreateDefaultTab();\n if (tabRequest) {\n this.workspaceManager.OpenTab(tabRequest, app.GetColor());\n }\n } else {\n // App has tabs - switch to the first one (or active one if exists)\n const config = this.workspaceManager.GetConfiguration();\n const activeAppTab = appTabs.find(t => t.id === config?.activeTabId);\n if (!activeAppTab) {\n // No active tab for this app, switch to first tab\n this.workspaceManager.SetActiveTab(appTabs[0].id);\n }\n }\n }\n\n /**\n * Update the query params for the currently active tab.\n * This updates the tab's configuration and triggers a URL sync via the shell's\n * workspace configuration subscription.\n *\n * Use this instead of directly calling router.navigate() to ensure proper\n * URL management that respects app-scoped routes.\n *\n * @param queryParams Object containing query param key-value pairs.\n * Use null values to remove a query param.\n * @example\n * // Add or update query params\n * navigationService.UpdateActiveTabQueryParams({ category: 'abc123', dashboard: 'xyz789' });\n *\n * // Remove a query param\n * navigationService.UpdateActiveTabQueryParams({ category: null });\n */\n UpdateActiveTabQueryParams(queryParams: Record<string, string | null>): void {\n const activeTabId = this.workspaceManager.GetActiveTabId();\n if (!activeTabId) {\n console.warn('NavigationService.UpdateActiveTabQueryParams: No active tab');\n return;\n }\n\n this.applyQueryParamsToTab(activeTabId, queryParams);\n }\n\n /**\n * Notify subscribers that query params changed on a specific tab.\n * Called by the shell when back/forward navigation changes query params on the active tab.\n * The notification includes the tab ID so only the component in that tab reacts.\n */\n NotifyQueryParamsChanged(tabId: string, params: Record<string, string>): void {\n this.queryParamChanged$.next({ TabId: tabId, Params: params });\n }\n\n /**\n * Apply query params to a specific tab by ID.\n * Merges with any existing query params on the tab. Use null values to remove params.\n */\n private applyQueryParamsToTab(tabId: string, queryParams: Record<string, string | null>): void {\n const tab = this.workspaceManager.GetTab(tabId);\n if (!tab) {\n console.warn('NavigationService.applyQueryParamsToTab: Tab not found:', tabId);\n return;\n }\n\n // Get existing queryParams from tab configuration\n const existingQueryParams = (tab.configuration?.['queryParams'] || {}) as Record<string, string | null>;\n\n // Merge with new query params\n const mergedQueryParams: Record<string, string> = {};\n\n // Start with existing params (excluding nulls)\n for (const [key, value] of Object.entries(existingQueryParams)) {\n if (value !== null) {\n mergedQueryParams[key] = value;\n }\n }\n\n // Apply new params (null means remove)\n for (const [key, value] of Object.entries(queryParams)) {\n if (value === null) {\n delete mergedQueryParams[key];\n } else {\n mergedQueryParams[key] = value;\n }\n }\n\n // Update the tab configuration\n this.workspaceManager.UpdateTabConfiguration(tabId, {\n queryParams: Object.keys(mergedQueryParams).length > 0 ? mergedQueryParams : undefined\n });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-shared",
3
- "version": "5.23.0",
3
+ "version": "5.25.0",
4
4
  "description": "MemberJunction: MJ Explorer Angular Shared Package - utility functions and other reusable elements used across other MJ Angular packages within the MJ Explorer App - do not use outside of MJ Explorer.",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -29,16 +29,16 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@angular/platform-browser": "21.1.3",
32
- "@memberjunction/ai-engine-base": "5.23.0",
33
- "@memberjunction/core": "5.23.0",
34
- "@memberjunction/core-entities": "5.23.0",
35
- "@memberjunction/entity-communications-base": "5.23.0",
36
- "@memberjunction/global": "5.23.0",
37
- "@memberjunction/graphql-dataprovider": "5.23.0",
38
- "@memberjunction/ng-base-application": "5.23.0",
39
- "@memberjunction/ng-base-types": "5.23.0",
40
- "@memberjunction/ng-notifications": "5.23.0",
41
- "@memberjunction/ng-shared-generic": "5.23.0",
32
+ "@memberjunction/ai-engine-base": "5.25.0",
33
+ "@memberjunction/core": "5.25.0",
34
+ "@memberjunction/core-entities": "5.25.0",
35
+ "@memberjunction/entity-communications-base": "5.25.0",
36
+ "@memberjunction/global": "5.25.0",
37
+ "@memberjunction/graphql-dataprovider": "5.25.0",
38
+ "@memberjunction/ng-base-application": "5.25.0",
39
+ "@memberjunction/ng-base-types": "5.25.0",
40
+ "@memberjunction/ng-notifications": "5.25.0",
41
+ "@memberjunction/ng-shared-generic": "5.25.0",
42
42
  "html-to-image": "^1.11.11",
43
43
  "tslib": "^2.8.1"
44
44
  },