@mj-biz-apps/common-ng 5.43.0 → 5.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/lib/components/related-chips/__tests__/related-links.test.d.ts +2 -0
  2. package/dist/lib/components/related-chips/__tests__/related-links.test.d.ts.map +1 -0
  3. package/dist/lib/components/related-chips/__tests__/related-links.test.js +183 -0
  4. package/dist/lib/components/related-chips/__tests__/related-links.test.js.map +1 -0
  5. package/dist/lib/components/related-chips/related-chips.component.d.ts +132 -0
  6. package/dist/lib/components/related-chips/related-chips.component.d.ts.map +1 -0
  7. package/dist/lib/components/related-chips/related-chips.component.js +241 -0
  8. package/dist/lib/components/related-chips/related-chips.component.js.map +1 -0
  9. package/dist/lib/components/related-chips/related-links.d.ts +151 -0
  10. package/dist/lib/components/related-chips/related-links.d.ts.map +1 -0
  11. package/dist/lib/components/related-chips/related-links.js +178 -0
  12. package/dist/lib/components/related-chips/related-links.js.map +1 -0
  13. package/dist/lib/components/selling-company-field/__tests__/selling-company-confirm.test.d.ts +2 -0
  14. package/dist/lib/components/selling-company-field/__tests__/selling-company-confirm.test.d.ts.map +1 -0
  15. package/dist/lib/components/selling-company-field/__tests__/selling-company-confirm.test.js +53 -0
  16. package/dist/lib/components/selling-company-field/__tests__/selling-company-confirm.test.js.map +1 -0
  17. package/dist/lib/components/selling-company-field/__tests__/selling-company-field.component.test.d.ts +2 -0
  18. package/dist/lib/components/selling-company-field/__tests__/selling-company-field.component.test.d.ts.map +1 -0
  19. package/dist/lib/components/selling-company-field/__tests__/selling-company-field.component.test.js +166 -0
  20. package/dist/lib/components/selling-company-field/__tests__/selling-company-field.component.test.js.map +1 -0
  21. package/dist/lib/components/selling-company-field/selling-company-confirm.d.ts +36 -0
  22. package/dist/lib/components/selling-company-field/selling-company-confirm.d.ts.map +1 -0
  23. package/dist/lib/components/selling-company-field/selling-company-confirm.js +54 -0
  24. package/dist/lib/components/selling-company-field/selling-company-confirm.js.map +1 -0
  25. package/dist/lib/components/selling-company-field/selling-company-field.component.d.ts +63 -0
  26. package/dist/lib/components/selling-company-field/selling-company-field.component.d.ts.map +1 -0
  27. package/dist/lib/components/selling-company-field/selling-company-field.component.js +221 -0
  28. package/dist/lib/components/selling-company-field/selling-company-field.component.js.map +1 -0
  29. package/dist/lib/data/__tests__/directory-headline.test.d.ts +2 -0
  30. package/dist/lib/data/__tests__/directory-headline.test.d.ts.map +1 -0
  31. package/dist/lib/data/__tests__/directory-headline.test.js +102 -0
  32. package/dist/lib/data/__tests__/directory-headline.test.js.map +1 -0
  33. package/dist/lib/data/__tests__/party-signal-store.test.d.ts +2 -0
  34. package/dist/lib/data/__tests__/party-signal-store.test.d.ts.map +1 -0
  35. package/dist/lib/data/__tests__/party-signal-store.test.js +98 -0
  36. package/dist/lib/data/__tests__/party-signal-store.test.js.map +1 -0
  37. package/dist/lib/data/common-settings.d.ts +18 -0
  38. package/dist/lib/data/common-settings.d.ts.map +1 -0
  39. package/dist/lib/data/common-settings.js +39 -0
  40. package/dist/lib/data/common-settings.js.map +1 -0
  41. package/dist/lib/data/directory-stats.d.ts +23 -1
  42. package/dist/lib/data/directory-stats.d.ts.map +1 -1
  43. package/dist/lib/data/directory-stats.js +40 -0
  44. package/dist/lib/data/directory-stats.js.map +1 -1
  45. package/dist/lib/data/directory-types.d.ts +34 -0
  46. package/dist/lib/data/directory-types.d.ts.map +1 -1
  47. package/dist/lib/data/party-signal-store.d.ts +39 -0
  48. package/dist/lib/data/party-signal-store.d.ts.map +1 -0
  49. package/dist/lib/data/party-signal-store.js +101 -0
  50. package/dist/lib/data/party-signal-store.js.map +1 -0
  51. package/dist/lib/pages/common-dashboard.page.d.ts +7 -7
  52. package/dist/lib/pages/common-dashboard.page.d.ts.map +1 -1
  53. package/dist/lib/pages/common-dashboard.page.js +228 -202
  54. package/dist/lib/pages/common-dashboard.page.js.map +1 -1
  55. package/dist/public-api.d.ts +6 -0
  56. package/dist/public-api.d.ts.map +1 -1
  57. package/dist/public-api.js +8 -0
  58. package/dist/public-api.js.map +1 -1
  59. package/package.json +2 -2
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=related-links.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-links.test.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/related-chips/__tests__/related-links.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,183 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { FilterForRelatedLink, LabelForRelatedLink, RelatedChipNavigation, ResolveRelatedChip, } from '../related-links';
3
+ /**
4
+ * These pin the rules that decide whether a chip may be DRAWN, not the markup that draws it.
5
+ *
6
+ * Every one of them is silent when it regresses: a dead chip looks exactly like a live one until
7
+ * somebody clicks it, and a GUID in place of a name looks like data rather than a bug.
8
+ */
9
+ const DEALS = {
10
+ Name: 'MJ_BizApps_Sales: Deals',
11
+ BaseTableDisplayName: 'Deal',
12
+ NameField: { Name: 'DealName' },
13
+ PrimaryKeys: [{ Name: 'ID' }],
14
+ };
15
+ const ORDERS = {
16
+ Name: 'MJ_BizApps_Orders: Order Headers',
17
+ BaseTableDisplayName: 'Order Header',
18
+ NameField: { Name: 'OrderNumber' },
19
+ PrimaryKeys: [{ Name: 'OrderKey' }],
20
+ };
21
+ const CATALOG = [DEALS, ORDERS];
22
+ /** A reader that always answers the same way, and records what it was asked. */
23
+ function readerReturning(result, asked = {}) {
24
+ return async (params) => {
25
+ asked.ExtraFilter = params.ExtraFilter;
26
+ asked.Fields = params.Fields;
27
+ return result;
28
+ };
29
+ }
30
+ function throwingReader() {
31
+ return async () => {
32
+ throw new Error('provider unreachable');
33
+ };
34
+ }
35
+ const DEAL_LINK = {
36
+ Key: 'source',
37
+ EntityName: DEALS.Name,
38
+ RecordID: 'D1',
39
+ LabelPrefix: 'Source',
40
+ };
41
+ describe('a chip that cannot navigate is not rendered', () => {
42
+ it('drops a link whose entity is not in the catalog — the app is not installed, or not readable', async () => {
43
+ const chip = await ResolveRelatedChip({ Key: 'contract', EntityName: 'MJ_BizApps_Contracts: Contracts', RecordID: 'C1' }, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'C1', Name: 'CTR-1' }] }));
44
+ expect(chip).toBeNull();
45
+ });
46
+ it('drops a link to an entity with no name field — a chip may not fall back to an id', async () => {
47
+ const nameless = { ...DEALS, NameField: null };
48
+ const chip = await ResolveRelatedChip(DEAL_LINK, [nameless], readerReturning({ Success: true, Results: [{}] }));
49
+ expect(chip).toBeNull();
50
+ });
51
+ it('drops a link that names neither a record nor a filter', async () => {
52
+ const chip = await ResolveRelatedChip({ Key: 'source', EntityName: DEALS.Name, RecordID: null }, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'D1', DealName: 'Acme' }] }));
53
+ expect(chip).toBeNull();
54
+ });
55
+ it('treats a blank RecordID as naming nothing', async () => {
56
+ const chip = await ResolveRelatedChip({ Key: 'source', EntityName: DEALS.Name, RecordID: ' ' }, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'D1', DealName: 'Acme' }] }));
57
+ expect(chip).toBeNull();
58
+ });
59
+ it('drops a link whose read SUCCEEDED and matched nothing — the record is not there', async () => {
60
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, readerReturning({ Success: true, Results: [] }));
61
+ expect(chip).toBeNull();
62
+ });
63
+ });
64
+ describe('a read that threw, a read that was refused, and a record that is absent', () => {
65
+ it('keeps a forward link when the read throws — the record may exist and merely be unreadable', async () => {
66
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, throwingReader());
67
+ expect(chip).not.toBeNull();
68
+ expect(chip?.RecordID).toBe('D1');
69
+ expect(chip?.Name).toBe('');
70
+ expect(chip?.Label).toBe('Source Deal');
71
+ });
72
+ it('drops a forward link when the read REPORTS failure — the server answered and refused', async () => {
73
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, readerReturning({ Success: false }));
74
+ expect(chip).toBeNull();
75
+ });
76
+ it('drops a reverse link when the read reports failure', async () => {
77
+ const chip = await ResolveRelatedChip({ Key: 'deal', EntityName: DEALS.Name, Filter: `OrderID = 'O1'` }, CATALOG, readerReturning({ Success: false }));
78
+ expect(chip).toBeNull();
79
+ });
80
+ it('keeps a forward link when the reader answers nothing at all — still unknown', async () => {
81
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, async () => undefined);
82
+ expect(chip?.RecordID).toBe('D1');
83
+ expect(chip?.Name).toBe('');
84
+ });
85
+ it('drops a REVERSE link when the read throws — the throw also cost us the id to open', async () => {
86
+ const chip = await ResolveRelatedChip({ Key: 'deal', EntityName: DEALS.Name, Filter: `OrderID = 'O1'` }, CATALOG, throwingReader());
87
+ expect(chip).toBeNull();
88
+ });
89
+ });
90
+ describe('a chip never shows a raw id', () => {
91
+ it('reads the name from the entity name field', async () => {
92
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'D1', DealName: 'Acme renewal' }] }));
93
+ expect(chip?.Name).toBe('Acme renewal');
94
+ });
95
+ it('asks for the primary key and the name field, and nothing else', async () => {
96
+ const asked = {};
97
+ await ResolveRelatedChip({ Key: 'order', EntityName: ORDERS.Name, RecordID: 'O1' }, CATALOG, readerReturning({ Success: true, Results: [{ OrderKey: 'O1', OrderNumber: 'ORD-9' }] }, asked));
98
+ expect(asked.Fields).toEqual(['OrderKey', 'OrderNumber']);
99
+ });
100
+ it('takes the id back from the row, so a reverse link knows what it found', async () => {
101
+ const chip = await ResolveRelatedChip({ Key: 'deal', EntityName: DEALS.Name, Filter: `OrderID = 'O1'` }, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'D7', DealName: 'Acme' }] }));
102
+ expect(chip?.RecordID).toBe('D7');
103
+ expect(chip?.Name).toBe('Acme');
104
+ });
105
+ });
106
+ describe('the filter', () => {
107
+ it('selects on the entity primary key, not a hardcoded ID column', () => {
108
+ expect(FilterForRelatedLink({ Key: 'o', EntityName: ORDERS.Name, RecordID: 'O1' }, ORDERS)).toBe(`OrderKey = 'O1'`);
109
+ });
110
+ it("escapes single quotes so an id cannot break out of the literal", () => {
111
+ expect(FilterForRelatedLink({ Key: 'd', EntityName: DEALS.Name, RecordID: "a'b" }, DEALS)).toBe(`ID = 'a''b'`);
112
+ });
113
+ it('prefers a RecordID over a Filter when both are given', () => {
114
+ expect(FilterForRelatedLink({ Key: 'd', EntityName: DEALS.Name, RecordID: 'D1', Filter: 'X = 1' }, DEALS)).toBe(`ID = 'D1'`);
115
+ });
116
+ it('passes a reverse filter through untouched', () => {
117
+ expect(FilterForRelatedLink({ Key: 'd', EntityName: DEALS.Name, Filter: `OrderID = 'O1'` }, DEALS)).toBe(`OrderID = 'O1'`);
118
+ });
119
+ });
120
+ describe('the label', () => {
121
+ it('uses an explicit Label above everything else', () => {
122
+ expect(LabelForRelatedLink({ Key: 'd', EntityName: DEALS.Name, Label: 'Won deal', LabelPrefix: 'Source' }, DEALS)).toBe('Won deal');
123
+ });
124
+ it('builds "<prefix> <singular entity>" from a LabelPrefix — "Source Deal"', () => {
125
+ expect(LabelForRelatedLink({ Key: 'd', EntityName: DEALS.Name, LabelPrefix: 'Source' }, DEALS)).toBe('Source Deal');
126
+ });
127
+ it('follows the entity for a polymorphic link, so Orders reads "Source Order Header"', () => {
128
+ expect(LabelForRelatedLink({ Key: 'd', EntityName: ORDERS.Name, LabelPrefix: 'Source' }, ORDERS)).toBe('Source Order Header');
129
+ });
130
+ it('falls back to the entity singular name when the caller names nothing', () => {
131
+ expect(LabelForRelatedLink({ Key: 'd', EntityName: DEALS.Name }, DEALS)).toBe('Deal');
132
+ });
133
+ });
134
+ describe('the accessible name', () => {
135
+ it('names the record when it could be read', async () => {
136
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, readerReturning({ Success: true, Results: [{ ID: 'D1', DealName: 'Acme' }] }));
137
+ expect(chip?.AriaLabel).toBe('Open Source Deal Acme');
138
+ });
139
+ it('does not trail an empty name when the record could not be read', async () => {
140
+ const chip = await ResolveRelatedChip(DEAL_LINK, CATALOG, throwingReader());
141
+ expect(chip?.AriaLabel).toBe('Open Source Deal');
142
+ });
143
+ });
144
+ describe('navigation', () => {
145
+ const chip = {
146
+ Key: 'order',
147
+ Label: 'Order',
148
+ Name: 'ORD-9',
149
+ Icon: null,
150
+ EntityName: ORDERS.Name,
151
+ PrimaryKeyField: 'OrderKey',
152
+ RecordID: 'O1',
153
+ AriaLabel: 'Open Order ORD-9',
154
+ };
155
+ function mouse(modifiers) {
156
+ return { ctrlKey: false, metaKey: false, ...modifiers };
157
+ }
158
+ /**
159
+ * NOT `toBe(false)`. `NavigationService.shouldForceNewTab` returns `options.forceNewTab`
160
+ * whenever it is `!== undefined` and only otherwise consults its global shift-key state, and
161
+ * Explorer passes `forceNewTab: event.OpenInNewTab` straight through — so an explicit `false`
162
+ * on a plain click silently disables shift-click for the whole row.
163
+ */
164
+ it('leaves OpenInNewTab UNSET on a plain click, so shift-click still reaches NavigationService', () => {
165
+ const event = RelatedChipNavigation(chip, mouse({}));
166
+ expect(event.OpenInNewTab).toBeUndefined();
167
+ expect('OpenInNewTab' in event).toBe(false);
168
+ });
169
+ it('opens a new tab on ctrl-click', () => {
170
+ expect(RelatedChipNavigation(chip, mouse({ ctrlKey: true })).OpenInNewTab).toBe(true);
171
+ });
172
+ it('opens a new tab on cmd-click, which is what a Mac user presses', () => {
173
+ expect(RelatedChipNavigation(chip, mouse({ metaKey: true })).OpenInNewTab).toBe(true);
174
+ });
175
+ it('keys on the entity own primary key column rather than assuming "ID"', () => {
176
+ const event = RelatedChipNavigation(chip, mouse({}));
177
+ expect(event.Kind).toBe('record');
178
+ expect(event.Kind === 'record' && event.PrimaryKey.KeyValuePairs).toEqual([
179
+ { FieldName: 'OrderKey', Value: 'O1' },
180
+ ]);
181
+ });
182
+ });
183
+ //# sourceMappingURL=related-links.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-links.test.js","sourceRoot":"","sources":["../../../../../src/lib/components/related-chips/__tests__/related-links.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import { EventEmitter, type OnDestroy } from '@angular/core';
2
+ import { type IMetadataProvider } from '@memberjunction/core';
3
+ import type { FormNavigationEvent } from '@memberjunction/ng-base-forms';
4
+ import { type BizAppsRelatedLink, type ResolvedRelatedChip } from './related-links';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * `bizapps-related-chips` — the row of links from a record to the records linked to it.
8
+ *
9
+ * THE SHARED BIZAPPS "RELATED" ROW. A tester walking Deal → Order → Contract found no consistent way
10
+ * to get from one record to the next: the Contract header linked its source Deal, the Deal form
11
+ * buried its Order in a panel, the Order form pointed at nothing, and where a link did exist it
12
+ * sometimes rendered a GUID. Each app had solved a slice of it differently. This is the one they
13
+ * collapse into.
14
+ *
15
+ * ## What a caller supplies, and what this owns
16
+ *
17
+ * The caller passes {@link Links} — descriptors naming an entity and either the id it holds or a
18
+ * filter that finds the record holding the id. Which links belong on which form is the caller's
19
+ * decision, because only the form knows its own record.
20
+ *
21
+ * Everything after that is this component's: resolving the entity, reading the record's NAME, and —
22
+ * the part worth having in one place — deciding when a chip must not be drawn at all. Those rules
23
+ * and their reasoning live in `related-links.ts`, deliberately outside this class so they can be
24
+ * tested without DI. In short: no chip for an entity this host does not have or this user cannot
25
+ * read, no chip for a record that is not there, and never a raw id in place of a name.
26
+ *
27
+ * ## Navigation goes up, not out
28
+ *
29
+ * The component emits {@link Navigate} and never touches `NavigationService`. A host form wires it
30
+ * to `FormComponent.OnFormNavigate($event)` — the same path every field link in these apps takes —
31
+ * which keeps this usable from a `BaseFormPanel` hero and from a form component override alike, and
32
+ * keeps the routing decision with the app that owns the surface.
33
+ *
34
+ * ## Example
35
+ *
36
+ * ```html
37
+ * <bizapps-related-chips
38
+ * [Links]="RelatedLinks"
39
+ * [Provider]="FormComponent?.ProviderToUse ?? null"
40
+ * (Navigate)="FormComponent.OnFormNavigate($event)" />
41
+ * ```
42
+ *
43
+ * ```typescript
44
+ * public RelatedLinks: BizAppsRelatedLink[] = [
45
+ * { Key: 'order', EntityName: 'MJ_BizApps_Orders: Order Headers', RecordID: deal.OrderID, Label: 'Order' },
46
+ * { Key: 'contract', EntityName: 'MJ_BizApps_Contracts: Contracts', RecordID: deal.ContractID, Label: 'Contract' },
47
+ * ];
48
+ * ```
49
+ */
50
+ export declare class RelatedChipsComponent implements OnDestroy {
51
+ private readonly cdr;
52
+ /** Chips that resolved. A link that resolved to nothing is absent here, never a blank chip. */
53
+ Chips: ResolvedRelatedChip[];
54
+ private links;
55
+ private provider;
56
+ /**
57
+ * Guards against a slower read for a PREVIOUS set of links landing after the caller moved on —
58
+ * a form navigating between records is the ordinary way that happens. Every resolve captures the
59
+ * generation it started in and discards itself if it no longer matches.
60
+ */
61
+ private generation;
62
+ /**
63
+ * The OTHER way a resolve becomes irrelevant, and the one the generation cannot see: the view it
64
+ * would publish into is gone. `detectChanges()` has no destroyed-view guard of its own, so a read
65
+ * still in flight when the user closes the tab would run change detection over a torn-down
66
+ * `LView`.
67
+ */
68
+ private destroyed;
69
+ /**
70
+ * The relationships to offer, in the order they should read.
71
+ *
72
+ * A setter rather than `ngOnChanges`, per MJ's rule: the input's own setter is the one place that
73
+ * knows the value changed. Assign a NEW array to trigger a re-resolve; mutating the existing one
74
+ * in place will not, which is the same contract every other data-driven MJ input has.
75
+ */
76
+ set Links(value: BizAppsRelatedLink[] | null | undefined);
77
+ get Links(): BizAppsRelatedLink[];
78
+ /**
79
+ * Metadata provider to read through. Falls back to the ambient one when not supplied.
80
+ *
81
+ * A form panel should pass `FormComponent.ProviderToUse`, so the chips read from the same place
82
+ * the record they describe came from. In a host with more than one provider the fallback reads
83
+ * the wrong database and the failure is a wrong ANSWER rather than an error, which is why this
84
+ * is worth passing even when there is only one today.
85
+ *
86
+ * A SETTER, and re-resolving, for the same reason. Angular assigns bound inputs in template
87
+ * order, and the usage above binds `[Links]` first — so a plain field would be read as `null` by
88
+ * the resolve the `Links` setter kicks off, silently reading the ambient provider, and would
89
+ * never be re-read once the real one arrived. `FormComponent?.ProviderToUse` starting out `null`
90
+ * and resolving a tick later has the same shape. Both resolve against the wrong database, and a
91
+ * wrong database here is a wrong answer rather than an error.
92
+ */
93
+ set Provider(value: IMetadataProvider | null | undefined);
94
+ get Provider(): IMetadataProvider | null;
95
+ /**
96
+ * Emitted when a chip is clicked, with `OpenInNewTab` set for ctrl/cmd-click.
97
+ *
98
+ * Wire it to the host form's `OnFormNavigate`. Nothing happens if it is left unbound — which is
99
+ * why the chip is drawn as a real `<button>` only because a click always has somewhere to go in
100
+ * practice; a host that binds nothing gets a row that looks live and is not.
101
+ */
102
+ Navigate: EventEmitter<FormNavigationEvent>;
103
+ /** The provider actually used — the one passed in, or the ambient one. */
104
+ get ProviderToUse(): IMetadataProvider;
105
+ Open(chip: ResolvedRelatedChip, event: MouseEvent): void;
106
+ ngOnDestroy(): void;
107
+ /**
108
+ * Drop what is on screen and queue a fresh resolve for the next microtask.
109
+ *
110
+ * CLEARED FIRST because the chips on screen describe the record we just left. A form container
111
+ * reuses this instance across records, so a header moving from Deal A to Deal B that kept A's
112
+ * chips for the length of the reads would offer a click that navigates to A's related record —
113
+ * a wrong destination, not a slow one.
114
+ *
115
+ * QUEUED because every input that feeds a resolve is assigned in the same change-detection pass,
116
+ * one after another. Resolving inside the setter would read a half-applied set of inputs, and
117
+ * would read twice. The microtask runs once both have landed; the generation it was queued under
118
+ * makes each superseded one drop itself.
119
+ */
120
+ private scheduleResolve;
121
+ /**
122
+ * Resolve every link concurrently, then publish the survivors in the caller's order.
123
+ *
124
+ * Published in one assignment rather than chip by chip: a row that grows an item at a time
125
+ * reflows the header under the reader, and the links are few enough that the slowest read is the
126
+ * whole wait either way.
127
+ */
128
+ private resolve;
129
+ static ɵfac: i0.ɵɵFactoryDeclaration<RelatedChipsComponent, never>;
130
+ static ɵcmp: i0.ɵɵComponentDeclaration<RelatedChipsComponent, "bizapps-related-chips", never, { "Links": { "alias": "Links"; "required": false; }; "Provider": { "alias": "Provider"; "required": false; }; }, { "Navigate": "Navigate"; }, never, never, true, never>;
131
+ }
132
+ //# sourceMappingURL=related-chips.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-chips.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/related-chips/related-chips.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAEZ,KAAK,SAAS,EAIjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAGH,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAC3B,MAAM,iBAAiB,CAAC;;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAiFa,qBAAsB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IAEjD,+FAA+F;IACxF,KAAK,EAAE,mBAAmB,EAAE,CAAM;IAEzC,OAAO,CAAC,KAAK,CAA4B;IAEzC,OAAO,CAAC,QAAQ,CAAkC;IAElD;;;;OAIG;IACH,OAAO,CAAC,UAAU,CAAK;IAEvB;;;;;OAKG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;;;OAMG;IACH,IACW,KAAK,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,IAAI,GAAG,SAAS,EAG9D;IACD,IAAW,KAAK,IAAI,kBAAkB,EAAE,CAEvC;IAED;;;;;;;;;;;;;;OAcG;IACH,IACW,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,EAG9D;IACD,IAAW,QAAQ,IAAI,iBAAiB,GAAG,IAAI,CAE9C;IAED;;;;;;OAMG;IACc,QAAQ,oCAA2C;IAEpE,0EAA0E;IAC1E,IAAW,aAAa,IAAI,iBAAiB,CAE5C;IAEM,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAKxD,WAAW,IAAI,IAAI;IAI1B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,eAAe;IAWvB;;;;;;OAMG;YACW,OAAO;yCAvHZ,qBAAqB;2CAArB,qBAAqB;CAgJjC"}
@@ -0,0 +1,241 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation, inject, } from '@angular/core';
2
+ import { Metadata, RunView } from '@memberjunction/core';
3
+ import { RelatedChipNavigation, ResolveRelatedChip, } from './related-links';
4
+ import * as i0 from "@angular/core";
5
+ const _forTrack0 = ($index, $item) => $item.Key;
6
+ function RelatedChipsComponent_Conditional_0_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
7
+ i0.ɵɵdomElement(0, "i", 6);
8
+ } if (rf & 2) {
9
+ const chip_r2 = i0.ɵɵnextContext().$implicit;
10
+ i0.ɵɵclassMap(chip_r2.Icon);
11
+ } }
12
+ function RelatedChipsComponent_Conditional_0_For_2_Template(rf, ctx) { if (rf & 1) {
13
+ const _r1 = i0.ɵɵgetCurrentView();
14
+ i0.ɵɵdomElementStart(0, "button", 2);
15
+ i0.ɵɵdomListener("click", function RelatedChipsComponent_Conditional_0_For_2_Template_button_click_0_listener($event) { const chip_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.Open(chip_r2, $event)); });
16
+ i0.ɵɵconditionalCreate(1, RelatedChipsComponent_Conditional_0_For_2_Conditional_1_Template, 1, 2, "i", 3);
17
+ i0.ɵɵdomElementStart(2, "span", 4);
18
+ i0.ɵɵtext(3);
19
+ i0.ɵɵdomElementEnd();
20
+ i0.ɵɵdomElementStart(4, "span", 5);
21
+ i0.ɵɵtext(5);
22
+ i0.ɵɵdomElementEnd()();
23
+ } if (rf & 2) {
24
+ const chip_r2 = ctx.$implicit;
25
+ i0.ɵɵattribute("aria-label", chip_r2.AriaLabel);
26
+ i0.ɵɵadvance();
27
+ i0.ɵɵconditional(chip_r2.Icon ? 1 : -1);
28
+ i0.ɵɵadvance(2);
29
+ i0.ɵɵtextInterpolate(chip_r2.Label);
30
+ i0.ɵɵadvance(2);
31
+ i0.ɵɵtextInterpolate(chip_r2.Name || "Open");
32
+ } }
33
+ function RelatedChipsComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
34
+ i0.ɵɵdomElementStart(0, "div", 0);
35
+ i0.ɵɵrepeaterCreate(1, RelatedChipsComponent_Conditional_0_For_2_Template, 6, 4, "button", 1, _forTrack0);
36
+ i0.ɵɵdomElementEnd();
37
+ } if (rf & 2) {
38
+ const ctx_r2 = i0.ɵɵnextContext();
39
+ i0.ɵɵadvance();
40
+ i0.ɵɵrepeater(ctx_r2.Chips);
41
+ } }
42
+ /**
43
+ * `bizapps-related-chips` — the row of links from a record to the records linked to it.
44
+ *
45
+ * THE SHARED BIZAPPS "RELATED" ROW. A tester walking Deal → Order → Contract found no consistent way
46
+ * to get from one record to the next: the Contract header linked its source Deal, the Deal form
47
+ * buried its Order in a panel, the Order form pointed at nothing, and where a link did exist it
48
+ * sometimes rendered a GUID. Each app had solved a slice of it differently. This is the one they
49
+ * collapse into.
50
+ *
51
+ * ## What a caller supplies, and what this owns
52
+ *
53
+ * The caller passes {@link Links} — descriptors naming an entity and either the id it holds or a
54
+ * filter that finds the record holding the id. Which links belong on which form is the caller's
55
+ * decision, because only the form knows its own record.
56
+ *
57
+ * Everything after that is this component's: resolving the entity, reading the record's NAME, and —
58
+ * the part worth having in one place — deciding when a chip must not be drawn at all. Those rules
59
+ * and their reasoning live in `related-links.ts`, deliberately outside this class so they can be
60
+ * tested without DI. In short: no chip for an entity this host does not have or this user cannot
61
+ * read, no chip for a record that is not there, and never a raw id in place of a name.
62
+ *
63
+ * ## Navigation goes up, not out
64
+ *
65
+ * The component emits {@link Navigate} and never touches `NavigationService`. A host form wires it
66
+ * to `FormComponent.OnFormNavigate($event)` — the same path every field link in these apps takes —
67
+ * which keeps this usable from a `BaseFormPanel` hero and from a form component override alike, and
68
+ * keeps the routing decision with the app that owns the surface.
69
+ *
70
+ * ## Example
71
+ *
72
+ * ```html
73
+ * <bizapps-related-chips
74
+ * [Links]="RelatedLinks"
75
+ * [Provider]="FormComponent?.ProviderToUse ?? null"
76
+ * (Navigate)="FormComponent.OnFormNavigate($event)" />
77
+ * ```
78
+ *
79
+ * ```typescript
80
+ * public RelatedLinks: BizAppsRelatedLink[] = [
81
+ * { Key: 'order', EntityName: 'MJ_BizApps_Orders: Order Headers', RecordID: deal.OrderID, Label: 'Order' },
82
+ * { Key: 'contract', EntityName: 'MJ_BizApps_Contracts: Contracts', RecordID: deal.ContractID, Label: 'Contract' },
83
+ * ];
84
+ * ```
85
+ */
86
+ export class RelatedChipsComponent {
87
+ cdr = inject(ChangeDetectorRef);
88
+ /** Chips that resolved. A link that resolved to nothing is absent here, never a blank chip. */
89
+ Chips = [];
90
+ links = [];
91
+ provider = null;
92
+ /**
93
+ * Guards against a slower read for a PREVIOUS set of links landing after the caller moved on —
94
+ * a form navigating between records is the ordinary way that happens. Every resolve captures the
95
+ * generation it started in and discards itself if it no longer matches.
96
+ */
97
+ generation = 0;
98
+ /**
99
+ * The OTHER way a resolve becomes irrelevant, and the one the generation cannot see: the view it
100
+ * would publish into is gone. `detectChanges()` has no destroyed-view guard of its own, so a read
101
+ * still in flight when the user closes the tab would run change detection over a torn-down
102
+ * `LView`.
103
+ */
104
+ destroyed = false;
105
+ /**
106
+ * The relationships to offer, in the order they should read.
107
+ *
108
+ * A setter rather than `ngOnChanges`, per MJ's rule: the input's own setter is the one place that
109
+ * knows the value changed. Assign a NEW array to trigger a re-resolve; mutating the existing one
110
+ * in place will not, which is the same contract every other data-driven MJ input has.
111
+ */
112
+ set Links(value) {
113
+ this.links = value ?? [];
114
+ this.scheduleResolve();
115
+ }
116
+ get Links() {
117
+ return this.links;
118
+ }
119
+ /**
120
+ * Metadata provider to read through. Falls back to the ambient one when not supplied.
121
+ *
122
+ * A form panel should pass `FormComponent.ProviderToUse`, so the chips read from the same place
123
+ * the record they describe came from. In a host with more than one provider the fallback reads
124
+ * the wrong database and the failure is a wrong ANSWER rather than an error, which is why this
125
+ * is worth passing even when there is only one today.
126
+ *
127
+ * A SETTER, and re-resolving, for the same reason. Angular assigns bound inputs in template
128
+ * order, and the usage above binds `[Links]` first — so a plain field would be read as `null` by
129
+ * the resolve the `Links` setter kicks off, silently reading the ambient provider, and would
130
+ * never be re-read once the real one arrived. `FormComponent?.ProviderToUse` starting out `null`
131
+ * and resolving a tick later has the same shape. Both resolve against the wrong database, and a
132
+ * wrong database here is a wrong answer rather than an error.
133
+ */
134
+ set Provider(value) {
135
+ this.provider = value ?? null;
136
+ this.scheduleResolve();
137
+ }
138
+ get Provider() {
139
+ return this.provider;
140
+ }
141
+ /**
142
+ * Emitted when a chip is clicked, with `OpenInNewTab` set for ctrl/cmd-click.
143
+ *
144
+ * Wire it to the host form's `OnFormNavigate`. Nothing happens if it is left unbound — which is
145
+ * why the chip is drawn as a real `<button>` only because a click always has somewhere to go in
146
+ * practice; a host that binds nothing gets a row that looks live and is not.
147
+ */
148
+ Navigate = new EventEmitter();
149
+ /** The provider actually used — the one passed in, or the ambient one. */
150
+ get ProviderToUse() {
151
+ return this.provider ?? Metadata.Provider;
152
+ }
153
+ Open(chip, event) {
154
+ event.preventDefault();
155
+ this.Navigate.emit(RelatedChipNavigation(chip, event));
156
+ }
157
+ ngOnDestroy() {
158
+ this.destroyed = true;
159
+ }
160
+ /**
161
+ * Drop what is on screen and queue a fresh resolve for the next microtask.
162
+ *
163
+ * CLEARED FIRST because the chips on screen describe the record we just left. A form container
164
+ * reuses this instance across records, so a header moving from Deal A to Deal B that kept A's
165
+ * chips for the length of the reads would offer a click that navigates to A's related record —
166
+ * a wrong destination, not a slow one.
167
+ *
168
+ * QUEUED because every input that feeds a resolve is assigned in the same change-detection pass,
169
+ * one after another. Resolving inside the setter would read a half-applied set of inputs, and
170
+ * would read twice. The microtask runs once both have landed; the generation it was queued under
171
+ * makes each superseded one drop itself.
172
+ */
173
+ scheduleResolve() {
174
+ const generation = ++this.generation;
175
+ this.Chips = [];
176
+ void Promise.resolve().then(() => {
177
+ if (this.generation !== generation || this.destroyed) {
178
+ return;
179
+ }
180
+ return this.resolve(generation);
181
+ });
182
+ }
183
+ /**
184
+ * Resolve every link concurrently, then publish the survivors in the caller's order.
185
+ *
186
+ * Published in one assignment rather than chip by chip: a row that grows an item at a time
187
+ * reflows the header under the reader, and the links are few enough that the slowest read is the
188
+ * whole wait either way.
189
+ */
190
+ async resolve(generation) {
191
+ const links = this.links;
192
+ if (links.length === 0) {
193
+ return;
194
+ }
195
+ const provider = this.ProviderToUse;
196
+ const entities = provider?.Entities ?? [];
197
+ const runView = RunView.FromMetadataProvider(provider);
198
+ const resolved = await Promise.all(links.map((link) => ResolveRelatedChip(link, entities, (params) => runView.RunView({ ...params, ResultType: 'simple' }))));
199
+ if (this.generation !== generation || this.destroyed) {
200
+ return;
201
+ }
202
+ this.Chips = resolved.filter((chip) => chip !== null);
203
+ this.cdr.detectChanges();
204
+ }
205
+ static ɵfac = function RelatedChipsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RelatedChipsComponent)(); };
206
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RelatedChipsComponent, selectors: [["bizapps-related-chips"]], inputs: { Links: "Links", Provider: "Provider" }, outputs: { Navigate: "Navigate" }, decls: 1, vars: 1, consts: [["role", "group", "aria-label", "Related records", 1, "bizapps-related"], ["type", "button", 1, "bizapps-related__chip"], ["type", "button", 1, "bizapps-related__chip", 3, "click"], ["aria-hidden", "true", 3, "class"], [1, "bizapps-related__label"], [1, "bizapps-related__name"], ["aria-hidden", "true"]], template: function RelatedChipsComponent_Template(rf, ctx) { if (rf & 1) {
207
+ i0.ɵɵconditionalCreate(0, RelatedChipsComponent_Conditional_0_Template, 3, 0, "div", 0);
208
+ } if (rf & 2) {
209
+ i0.ɵɵconditional(ctx.Chips.length > 0 ? 0 : -1);
210
+ } }, styles: ["\n /* Styles are the component's own and use design tokens only, under a class prefix of\n their own: the app kits (orders-kit, contracts-kit) are global under\n ViewEncapsulation.None, and .mj-chip / .mjc-chip / .mjo-* / .biz-hero__chip are all\n already taken by unrelated things. A row that borrowed one of those would render\n differently in each app, which is the failure this component exists to end. */\n .bizapps-related {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n }\n .bizapps-related__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-1-5);\n max-width: 100%;\n margin: 0;\n padding: var(--mj-space-1) var(--mj-space-3);\n font-family: inherit;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n line-height: 1.5;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n }\n .bizapps-related__chip:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-brand-primary);\n }\n .bizapps-related__chip:focus-visible {\n outline: var(--mj-ring-width) solid var(--mj-focus-ring-color);\n outline-offset: var(--mj-ring-offset);\n }\n /* The relationship is the quiet half; the record's NAME is what the reader is looking\n for, so it carries the link colour and the underline on hover. */\n .bizapps-related__label {\n color: var(--mj-text-secondary);\n white-space: nowrap;\n }\n .bizapps-related__name {\n overflow: hidden;\n color: var(--mj-text-link);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .bizapps-related__chip:hover .bizapps-related__name {\n color: var(--mj-text-link-hover);\n text-decoration: underline;\n }\n "], encapsulation: 2, changeDetection: 0 });
211
+ }
212
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RelatedChipsComponent, [{
213
+ type: Component,
214
+ args: [{ selector: 'bizapps-related-chips', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: `
215
+ @if (Chips.length > 0) {
216
+ <div class="bizapps-related" role="group" aria-label="Related records">
217
+ @for (chip of Chips; track chip.Key) {
218
+ <button
219
+ type="button"
220
+ class="bizapps-related__chip"
221
+ [attr.aria-label]="chip.AriaLabel"
222
+ (click)="Open(chip, $event)">
223
+ @if (chip.Icon) {
224
+ <i [class]="chip.Icon" aria-hidden="true"></i>
225
+ }
226
+ <span class="bizapps-related__label">{{ chip.Label }}</span>
227
+ <span class="bizapps-related__name">{{ chip.Name || 'Open' }}</span>
228
+ </button>
229
+ }
230
+ </div>
231
+ }
232
+ `, styles: ["\n /* Styles are the component's own and use design tokens only, under a class prefix of\n their own: the app kits (orders-kit, contracts-kit) are global under\n ViewEncapsulation.None, and .mj-chip / .mjc-chip / .mjo-* / .biz-hero__chip are all\n already taken by unrelated things. A row that borrowed one of those would render\n differently in each app, which is the failure this component exists to end. */\n .bizapps-related {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n }\n .bizapps-related__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-1-5);\n max-width: 100%;\n margin: 0;\n padding: var(--mj-space-1) var(--mj-space-3);\n font-family: inherit;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n line-height: 1.5;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n }\n .bizapps-related__chip:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-brand-primary);\n }\n .bizapps-related__chip:focus-visible {\n outline: var(--mj-ring-width) solid var(--mj-focus-ring-color);\n outline-offset: var(--mj-ring-offset);\n }\n /* The relationship is the quiet half; the record's NAME is what the reader is looking\n for, so it carries the link colour and the underline on hover. */\n .bizapps-related__label {\n color: var(--mj-text-secondary);\n white-space: nowrap;\n }\n .bizapps-related__name {\n overflow: hidden;\n color: var(--mj-text-link);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .bizapps-related__chip:hover .bizapps-related__name {\n color: var(--mj-text-link-hover);\n text-decoration: underline;\n }\n "] }]
233
+ }], null, { Links: [{
234
+ type: Input
235
+ }], Provider: [{
236
+ type: Input
237
+ }], Navigate: [{
238
+ type: Output
239
+ }] }); })();
240
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RelatedChipsComponent, { className: "RelatedChipsComponent", filePath: "lib/components/related-chips/related-chips.component.ts", lineNumber: 146 }); })();
241
+ //# sourceMappingURL=related-chips.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-chips.component.js","sourceRoot":"","sources":["../../../../src/lib/components/related-chips/related-chips.component.ts"],"names":[],"mappings":"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"}