@mj-biz-apps/common-ng 5.42.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/related-chips/__tests__/related-links.test.d.ts +2 -0
- package/dist/lib/components/related-chips/__tests__/related-links.test.d.ts.map +1 -0
- package/dist/lib/components/related-chips/__tests__/related-links.test.js +183 -0
- package/dist/lib/components/related-chips/__tests__/related-links.test.js.map +1 -0
- package/dist/lib/components/related-chips/related-chips.component.d.ts +132 -0
- package/dist/lib/components/related-chips/related-chips.component.d.ts.map +1 -0
- package/dist/lib/components/related-chips/related-chips.component.js +241 -0
- package/dist/lib/components/related-chips/related-chips.component.js.map +1 -0
- package/dist/lib/components/related-chips/related-links.d.ts +151 -0
- package/dist/lib/components/related-chips/related-links.d.ts.map +1 -0
- package/dist/lib/components/related-chips/related-links.js +178 -0
- package/dist/lib/components/related-chips/related-links.js.map +1 -0
- package/dist/lib/data/__tests__/directory-headline.test.d.ts +2 -0
- package/dist/lib/data/__tests__/directory-headline.test.d.ts.map +1 -0
- package/dist/lib/data/__tests__/directory-headline.test.js +102 -0
- package/dist/lib/data/__tests__/directory-headline.test.js.map +1 -0
- package/dist/lib/data/directory-stats.d.ts +23 -1
- package/dist/lib/data/directory-stats.d.ts.map +1 -1
- package/dist/lib/data/directory-stats.js +40 -0
- package/dist/lib/data/directory-stats.js.map +1 -1
- package/dist/lib/data/directory-types.d.ts +34 -0
- package/dist/lib/data/directory-types.d.ts.map +1 -1
- package/dist/lib/pages/common-dashboard.page.d.ts +7 -7
- package/dist/lib/pages/common-dashboard.page.d.ts.map +1 -1
- package/dist/lib/pages/common-dashboard.page.js +228 -202
- package/dist/lib/pages/common-dashboard.page.js.map +1 -1
- package/dist/public-api.d.ts +2 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +2 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +16 -17
|
@@ -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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"}
|