@memberjunction/ng-record-clone 0.0.1-seed.1 → 6.2.0-edge.2

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 (55) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +241 -3
  3. package/dist/lib/clone-lineage-chip.component.d.ts +50 -0
  4. package/dist/lib/clone-lineage-chip.component.d.ts.map +1 -0
  5. package/dist/lib/clone-lineage-chip.component.js +448 -0
  6. package/dist/lib/clone-lineage-chip.component.js.map +1 -0
  7. package/dist/lib/clone-plan-tree.component.d.ts +51 -0
  8. package/dist/lib/clone-plan-tree.component.d.ts.map +1 -0
  9. package/dist/lib/clone-plan-tree.component.js +634 -0
  10. package/dist/lib/clone-plan-tree.component.js.map +1 -0
  11. package/dist/lib/clone-progress.component.d.ts +12 -0
  12. package/dist/lib/clone-progress.component.d.ts.map +1 -0
  13. package/dist/lib/clone-progress.component.js +151 -0
  14. package/dist/lib/clone-progress.component.js.map +1 -0
  15. package/dist/lib/clone-result.component.d.ts +40 -0
  16. package/dist/lib/clone-result.component.d.ts.map +1 -0
  17. package/dist/lib/clone-result.component.js +357 -0
  18. package/dist/lib/clone-result.component.js.map +1 -0
  19. package/dist/lib/clone-review.component.d.ts +58 -0
  20. package/dist/lib/clone-review.component.d.ts.map +1 -0
  21. package/dist/lib/clone-review.component.js +581 -0
  22. package/dist/lib/clone-review.component.js.map +1 -0
  23. package/dist/lib/clone-scope-controls.component.d.ts +81 -0
  24. package/dist/lib/clone-scope-controls.component.d.ts.map +1 -0
  25. package/dist/lib/clone-scope-controls.component.js +394 -0
  26. package/dist/lib/clone-scope-controls.component.js.map +1 -0
  27. package/dist/lib/clone-values.component.d.ts +55 -0
  28. package/dist/lib/clone-values.component.d.ts.map +1 -0
  29. package/dist/lib/clone-values.component.js +493 -0
  30. package/dist/lib/clone-values.component.js.map +1 -0
  31. package/dist/lib/record-clone-panel.component.d.ts +247 -0
  32. package/dist/lib/record-clone-panel.component.d.ts.map +1 -0
  33. package/dist/lib/record-clone-panel.component.js +1252 -0
  34. package/dist/lib/record-clone-panel.component.js.map +1 -0
  35. package/dist/lib/record-clone-slide-in.component.d.ts +83 -0
  36. package/dist/lib/record-clone-slide-in.component.d.ts.map +1 -0
  37. package/dist/lib/record-clone-slide-in.component.js +206 -0
  38. package/dist/lib/record-clone-slide-in.component.js.map +1 -0
  39. package/dist/lib/record-clone-types.d.ts +89 -0
  40. package/dist/lib/record-clone-types.d.ts.map +1 -0
  41. package/dist/lib/record-clone-types.js +29 -0
  42. package/dist/lib/record-clone-types.js.map +1 -0
  43. package/dist/lib/record-clone.module.d.ts +16 -0
  44. package/dist/lib/record-clone.module.d.ts.map +1 -0
  45. package/dist/lib/record-clone.module.js +69 -0
  46. package/dist/lib/record-clone.module.js.map +1 -0
  47. package/dist/lib/record-clone.service.d.ts +42 -0
  48. package/dist/lib/record-clone.service.d.ts.map +1 -0
  49. package/dist/lib/record-clone.service.js +123 -0
  50. package/dist/lib/record-clone.service.js.map +1 -0
  51. package/dist/public-api.d.ts +16 -0
  52. package/dist/public-api.d.ts.map +1 -0
  53. package/dist/public-api.js +16 -0
  54. package/dist/public-api.js.map +1 -0
  55. package/package.json +47 -5
@@ -0,0 +1,448 @@
1
+ /**
2
+ * @fileoverview Compact lineage chip and history popover component.
3
+ *
4
+ * Implements §12.3 of the Record Cloning architectural blueprint. Surfaces origin
5
+ * and clone lineage metadata ("Cloned from <name>" or "<n> clones"). Clicking opens
6
+ * a popover listing ancestral origins and descendant clones with safe navigation links.
7
+ */
8
+ import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, ViewChild, inject, } from '@angular/core';
9
+ import { CommonModule } from '@angular/common';
10
+ import { CompositeKey } from '@memberjunction/core';
11
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
12
+ import { RecordCloneService } from './record-clone.service';
13
+ import { CompositeKeyToRecordCloneKey } from './record-clone-types';
14
+ import * as i0 from "@angular/core";
15
+ const _c0 = ["chipTrigger"];
16
+ const _c1 = ["closeButton"];
17
+ const _forTrack0 = ($index, $item) => $item.CloneLogID;
18
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_For_5_Conditional_7_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵtext(0);
20
+ } if (rf & 2) {
21
+ const item_r5 = i0.ɵɵnextContext().$implicit;
22
+ i0.ɵɵtextInterpolate1(" \u2022 by ", item_r5.ClonedBy, " ");
23
+ } }
24
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_For_5_Template(rf, ctx) { if (rf & 1) {
25
+ const _r4 = i0.ɵɵgetCurrentView();
26
+ i0.ɵɵdomElementStart(0, "div", 16);
27
+ i0.ɵɵdomElement(1, "i", 17);
28
+ i0.ɵɵdomElementStart(2, "div", 18)(3, "button", 19);
29
+ i0.ɵɵdomListener("click", function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_For_5_Template_button_click_3_listener() { const item_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.OnNavigate(item_r5)); });
30
+ i0.ɵɵtext(4);
31
+ i0.ɵɵdomElementEnd();
32
+ i0.ɵɵdomElementStart(5, "span", 20);
33
+ i0.ɵɵtext(6);
34
+ i0.ɵɵconditionalCreate(7, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_For_5_Conditional_7_Template, 1, 1);
35
+ i0.ɵɵdomElementEnd()()();
36
+ } if (rf & 2) {
37
+ const item_r5 = ctx.$implicit;
38
+ i0.ɵɵadvance(4);
39
+ i0.ɵɵtextInterpolate1(" ", item_r5.DisplayName || item_r5.RecordID, " ");
40
+ i0.ɵɵadvance(2);
41
+ i0.ɵɵtextInterpolate1(" ", item_r5.EntityName, " ");
42
+ i0.ɵɵadvance();
43
+ i0.ɵɵconditional(item_r5.ClonedBy ? 7 : -1);
44
+ } }
45
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_Template(rf, ctx) { if (rf & 1) {
46
+ i0.ɵɵdomElementStart(0, "div", 13)(1, "span", 14);
47
+ i0.ɵɵtext(2, "Ancestry Chain (Origins)");
48
+ i0.ɵɵdomElementEnd();
49
+ i0.ɵɵdomElementStart(3, "div", 15);
50
+ i0.ɵɵrepeaterCreate(4, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_For_5_Template, 8, 3, "div", 16, _forTrack0);
51
+ i0.ɵɵdomElementEnd()();
52
+ } if (rf & 2) {
53
+ const ctx_r1 = i0.ɵɵnextContext(3);
54
+ i0.ɵɵadvance(4);
55
+ i0.ɵɵrepeater(ctx_r1.Ancestors);
56
+ } }
57
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_10_Conditional_10_Template(rf, ctx) { if (rf & 1) {
58
+ i0.ɵɵtext(0);
59
+ } if (rf & 2) {
60
+ const ctx_r1 = i0.ɵɵnextContext(4);
61
+ i0.ɵɵtextInterpolate1(" \u2022 by ", ctx_r1.SourceRecord.ClonedBy, " ");
62
+ } }
63
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_10_Template(rf, ctx) { if (rf & 1) {
64
+ const _r6 = i0.ɵɵgetCurrentView();
65
+ i0.ɵɵdomElementStart(0, "div", 13)(1, "span", 14);
66
+ i0.ɵɵtext(2, "Cloned From");
67
+ i0.ɵɵdomElementEnd();
68
+ i0.ɵɵdomElementStart(3, "div", 16);
69
+ i0.ɵɵdomElement(4, "i", 17);
70
+ i0.ɵɵdomElementStart(5, "div", 18)(6, "button", 19);
71
+ i0.ɵɵdomListener("click", function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_10_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnNavigate(ctx_r1.SourceRecord)); });
72
+ i0.ɵɵtext(7);
73
+ i0.ɵɵdomElementEnd();
74
+ i0.ɵɵdomElementStart(8, "span", 20);
75
+ i0.ɵɵtext(9);
76
+ i0.ɵɵconditionalCreate(10, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_10_Conditional_10_Template, 1, 1);
77
+ i0.ɵɵdomElementEnd()()()();
78
+ } if (rf & 2) {
79
+ const ctx_r1 = i0.ɵɵnextContext(3);
80
+ i0.ɵɵadvance(7);
81
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.SourceRecord.DisplayName || ctx_r1.SourceRecord.RecordID, " ");
82
+ i0.ɵɵadvance(2);
83
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.SourceRecord.EntityName, " ");
84
+ i0.ɵɵadvance();
85
+ i0.ɵɵconditional(ctx_r1.SourceRecord.ClonedBy ? 10 : -1);
86
+ } }
87
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_For_5_Conditional_7_Template(rf, ctx) { if (rf & 1) {
88
+ i0.ɵɵtext(0);
89
+ } if (rf & 2) {
90
+ const clone_r8 = i0.ɵɵnextContext().$implicit;
91
+ i0.ɵɵtextInterpolate1(" \u2022 by ", clone_r8.ClonedBy, " ");
92
+ } }
93
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_For_5_Template(rf, ctx) { if (rf & 1) {
94
+ const _r7 = i0.ɵɵgetCurrentView();
95
+ i0.ɵɵdomElementStart(0, "div", 16);
96
+ i0.ɵɵdomElement(1, "i", 21);
97
+ i0.ɵɵdomElementStart(2, "div", 18)(3, "button", 19);
98
+ i0.ɵɵdomListener("click", function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_For_5_Template_button_click_3_listener() { const clone_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.OnNavigate(clone_r8)); });
99
+ i0.ɵɵtext(4);
100
+ i0.ɵɵdomElementEnd();
101
+ i0.ɵɵdomElementStart(5, "span", 20);
102
+ i0.ɵɵtext(6);
103
+ i0.ɵɵconditionalCreate(7, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_For_5_Conditional_7_Template, 1, 1);
104
+ i0.ɵɵdomElementEnd()()();
105
+ } if (rf & 2) {
106
+ const clone_r8 = ctx.$implicit;
107
+ i0.ɵɵadvance(4);
108
+ i0.ɵɵtextInterpolate1(" ", clone_r8.DisplayName || clone_r8.RecordID, " ");
109
+ i0.ɵɵadvance(2);
110
+ i0.ɵɵtextInterpolate1(" ", clone_r8.EntityName, " ");
111
+ i0.ɵɵadvance();
112
+ i0.ɵɵconditional(clone_r8.ClonedBy ? 7 : -1);
113
+ } }
114
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_Template(rf, ctx) { if (rf & 1) {
115
+ i0.ɵɵdomElementStart(0, "div", 13)(1, "span", 14);
116
+ i0.ɵɵtext(2);
117
+ i0.ɵɵdomElementEnd();
118
+ i0.ɵɵdomElementStart(3, "div", 15);
119
+ i0.ɵɵrepeaterCreate(4, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_For_5_Template, 8, 3, "div", 16, _forTrack0);
120
+ i0.ɵɵdomElementEnd()();
121
+ } if (rf & 2) {
122
+ const ctx_r1 = i0.ɵɵnextContext(3);
123
+ i0.ɵɵadvance(2);
124
+ i0.ɵɵtextInterpolate1(" Clones Created from This Record (", ctx_r1.Clones.length, ") ");
125
+ i0.ɵɵadvance(2);
126
+ i0.ɵɵrepeater(ctx_r1.Clones);
127
+ } }
128
+ function CloneLineageChipComponent_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
129
+ const _r3 = i0.ɵɵgetCurrentView();
130
+ i0.ɵɵdomElementStart(0, "div", 6)(1, "div", 7)(2, "span", 8);
131
+ i0.ɵɵdomElement(3, "i", 9);
132
+ i0.ɵɵtext(4, " Record Clone Lineage ");
133
+ i0.ɵɵdomElementEnd();
134
+ i0.ɵɵdomElementStart(5, "button", 10, 1);
135
+ i0.ɵɵdomListener("click", function CloneLineageChipComponent_Conditional_0_Conditional_6_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ClosePopover(true)); });
136
+ i0.ɵɵdomElement(7, "i", 11);
137
+ i0.ɵɵdomElementEnd()();
138
+ i0.ɵɵdomElementStart(8, "div", 12);
139
+ i0.ɵɵconditionalCreate(9, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_9_Template, 6, 0, "div", 13);
140
+ i0.ɵɵconditionalCreate(10, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_10_Template, 11, 3, "div", 13);
141
+ i0.ɵɵconditionalCreate(11, CloneLineageChipComponent_Conditional_0_Conditional_6_Conditional_11_Template, 6, 1, "div", 13);
142
+ i0.ɵɵdomElementEnd()();
143
+ } if (rf & 2) {
144
+ const ctx_r1 = i0.ɵɵnextContext(2);
145
+ i0.ɵɵdomProperty("id", ctx_r1.PopoverId);
146
+ i0.ɵɵadvance(9);
147
+ i0.ɵɵconditional(ctx_r1.Ancestors && ctx_r1.Ancestors.length > 0 ? 9 : -1);
148
+ i0.ɵɵadvance();
149
+ i0.ɵɵconditional(ctx_r1.SourceRecord && (!ctx_r1.Ancestors || ctx_r1.Ancestors.length === 0) ? 10 : -1);
150
+ i0.ɵɵadvance();
151
+ i0.ɵɵconditional(ctx_r1.Clones && ctx_r1.Clones.length > 0 ? 11 : -1);
152
+ } }
153
+ function CloneLineageChipComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
154
+ const _r1 = i0.ɵɵgetCurrentView();
155
+ i0.ɵɵdomElementStart(0, "div", 3);
156
+ i0.ɵɵdomListener("keydown.escape", function CloneLineageChipComponent_Conditional_0_Template_div_keydown_escape_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnEscape($event)); });
157
+ i0.ɵɵdomElementStart(1, "button", 4, 0);
158
+ i0.ɵɵdomListener("click", function CloneLineageChipComponent_Conditional_0_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.TogglePopover()); });
159
+ i0.ɵɵdomElement(3, "i");
160
+ i0.ɵɵdomElementStart(4, "span", 5);
161
+ i0.ɵɵtext(5);
162
+ i0.ɵɵdomElementEnd()();
163
+ i0.ɵɵconditionalCreate(6, CloneLineageChipComponent_Conditional_0_Conditional_6_Template, 12, 4, "div", 6);
164
+ i0.ɵɵdomElementEnd();
165
+ } if (rf & 2) {
166
+ const ctx_r1 = i0.ɵɵnextContext();
167
+ i0.ɵɵadvance();
168
+ i0.ɵɵclassProp("active", ctx_r1.IsOpen);
169
+ i0.ɵɵdomProperty("title", ctx_r1.ChipTooltip);
170
+ i0.ɵɵattribute("aria-expanded", ctx_r1.IsOpen)("aria-controls", ctx_r1.PopoverId);
171
+ i0.ɵɵadvance(2);
172
+ i0.ɵɵclassMap(ctx_r1.ChipIcon);
173
+ i0.ɵɵadvance(2);
174
+ i0.ɵɵtextInterpolate(ctx_r1.ChipLabel);
175
+ i0.ɵɵadvance();
176
+ i0.ɵɵconditional(ctx_r1.IsOpen ? 6 : -1);
177
+ } }
178
+ export class CloneLineageChipComponent extends BaseAngularComponent {
179
+ cloneService = inject(RecordCloneService);
180
+ cdr = inject(ChangeDetectorRef);
181
+ /** Entity of the record whose lineage to show. */
182
+ EntityName = '';
183
+ /** Key of the record: a RecordCloneKey or a record-id string (`CompositeKey.FromURLSegment` form). */
184
+ RecordKey = '';
185
+ /** Load lineage on init. Set false and call `LoadLineage()` to control timing. */
186
+ AutoLoad = true;
187
+ /** Pre-loaded lineage; skips the server call when set. */
188
+ LineageData = null;
189
+ /** Asks the host to open an ancestor or clone from the popover. */
190
+ NavigateToRecord = new EventEmitter();
191
+ IsOpen = false;
192
+ IsLoading = false;
193
+ get SourceRecord() {
194
+ if (!this.LineageData?.Ancestors || this.LineageData.Ancestors.length === 0)
195
+ return undefined;
196
+ return this.LineageData.Ancestors[this.LineageData.Ancestors.length - 1];
197
+ }
198
+ get Ancestors() {
199
+ return this.LineageData?.Ancestors;
200
+ }
201
+ get Clones() {
202
+ return this.LineageData?.Clones;
203
+ }
204
+ get HasLineage() {
205
+ return !!this.SourceRecord || (!!this.Clones && this.Clones.length > 0);
206
+ }
207
+ get ChipIcon() {
208
+ if (this.SourceRecord) {
209
+ return 'fa-solid fa-code-branch';
210
+ }
211
+ return 'fa-solid fa-clone';
212
+ }
213
+ get ChipLabel() {
214
+ if (this.SourceRecord) {
215
+ return `Cloned from ${this.SourceRecord.DisplayName || this.SourceRecord.RecordID}`;
216
+ }
217
+ if (this.Clones && this.Clones.length > 0) {
218
+ return `${this.Clones.length} clone${this.Clones.length === 1 ? '' : 's'}`;
219
+ }
220
+ return 'Lineage';
221
+ }
222
+ get ChipTooltip() {
223
+ if (this.SourceRecord) {
224
+ return `View clone origin and lineage history`;
225
+ }
226
+ return `View records cloned from this record`;
227
+ }
228
+ async ngOnInit() {
229
+ if (this.AutoLoad && !this.LineageData && this.EntityName && this.RecordKey) {
230
+ await this.LoadLineage();
231
+ }
232
+ }
233
+ async LoadLineage() {
234
+ this.IsLoading = true;
235
+ this.cdr.markForCheck();
236
+ try {
237
+ const key = typeof this.RecordKey === 'string'
238
+ ? CompositeKeyToRecordCloneKey(CompositeKey.FromURLSegment(this.ProviderToUse?.EntityByName(this.EntityName), this.RecordKey))
239
+ : this.RecordKey;
240
+ this.LineageData = await this.cloneService.GetLineage({
241
+ EntityName: this.EntityName,
242
+ Key: key,
243
+ Direction: 'both',
244
+ }, this.ProviderToUse);
245
+ }
246
+ catch {
247
+ this.LineageData = null;
248
+ }
249
+ finally {
250
+ this.IsLoading = false;
251
+ this.cdr.markForCheck();
252
+ }
253
+ }
254
+ static nextId = 0;
255
+ /** Ties the chip's aria-controls to its popover. */
256
+ PopoverId = `mj-clone-lineage-${++CloneLineageChipComponent.nextId}`;
257
+ chipTrigger;
258
+ closeButton;
259
+ TogglePopover() {
260
+ this.IsOpen = !this.IsOpen;
261
+ this.cdr.detectChanges();
262
+ // Move focus into the panel on open, so keyboard users land in it.
263
+ if (this.IsOpen)
264
+ this.closeButton?.nativeElement.focus();
265
+ }
266
+ /** Closes the popover; `returnFocus` puts focus back on the chip (Escape, the close button). */
267
+ ClosePopover(returnFocus = false) {
268
+ this.IsOpen = false;
269
+ this.cdr.markForCheck();
270
+ if (returnFocus)
271
+ this.chipTrigger?.nativeElement.focus();
272
+ }
273
+ OnEscape(event) {
274
+ if (!this.IsOpen)
275
+ return;
276
+ event.stopPropagation();
277
+ this.ClosePopover(true);
278
+ }
279
+ OnNavigate(item) {
280
+ // Back to the chip: the host may open the record in another tab, leaving this page as it was.
281
+ this.ClosePopover(true);
282
+ this.NavigateToRecord.emit({
283
+ Kind: 'record',
284
+ EntityName: item.EntityName,
285
+ RecordKey: item.RecordID,
286
+ });
287
+ }
288
+ static ɵfac = /*@__PURE__*/ (() => { let ɵCloneLineageChipComponent_BaseFactory; return function CloneLineageChipComponent_Factory(__ngFactoryType__) { return (ɵCloneLineageChipComponent_BaseFactory || (ɵCloneLineageChipComponent_BaseFactory = i0.ɵɵgetInheritedFactory(CloneLineageChipComponent)))(__ngFactoryType__ || CloneLineageChipComponent); }; })();
289
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CloneLineageChipComponent, selectors: [["mj-clone-lineage-chip"]], viewQuery: function CloneLineageChipComponent_Query(rf, ctx) { if (rf & 1) {
290
+ i0.ɵɵviewQuery(_c0, 5)(_c1, 5);
291
+ } if (rf & 2) {
292
+ let _t;
293
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chipTrigger = _t.first);
294
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.closeButton = _t.first);
295
+ } }, inputs: { EntityName: "EntityName", RecordKey: "RecordKey", AutoLoad: "AutoLoad", LineageData: "LineageData" }, outputs: { NavigateToRecord: "NavigateToRecord" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 1, vars: 1, consts: [["chipTrigger", ""], ["closeButton", ""], [1, "lineage-wrapper"], [1, "lineage-wrapper", 3, "keydown.escape"], ["type", "button", 1, "lineage-chip-btn", 3, "click", "title"], [1, "chip-label"], ["role", "region", "aria-label", "Record lineage", 1, "lineage-popover", 3, "id"], [1, "popover-header"], [1, "popover-title"], [1, "fa-solid", "fa-code-fork"], ["type", "button", "aria-label", "Close lineage", "title", "Close", 1, "popover-close-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "popover-body"], [1, "lineage-group"], [1, "group-label"], [1, "lineage-items-list"], [1, "lineage-item"], [1, "fa-solid", "fa-arrow-turn-up", "item-icon", "ancestor-icon"], [1, "item-content"], ["type", "button", 1, "item-link", 3, "click"], [1, "item-meta"], [1, "fa-solid", "fa-clone", "item-icon", "clone-icon"]], template: function CloneLineageChipComponent_Template(rf, ctx) { if (rf & 1) {
296
+ i0.ɵɵconditionalCreate(0, CloneLineageChipComponent_Conditional_0_Template, 7, 9, "div", 2);
297
+ } if (rf & 2) {
298
+ i0.ɵɵconditional(ctx.HasLineage ? 0 : -1);
299
+ } }, dependencies: [CommonModule], styles: [".lineage-wrapper[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n }\n\n .lineage-chip-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 8px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n font-size: var(--mj-text-xs);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease-in-out;\n user-select: none;\n }\n\n .lineage-chip-btn[_ngcontent-%COMP%]:hover, .lineage-chip-btn.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n }\n\n .chip-label[_ngcontent-%COMP%] {\n font-weight: 500;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .lineage-popover[_ngcontent-%COMP%] {\n position: absolute;\n top: 100%;\n left: 0;\n margin-top: 4px;\n width: 280px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .popover-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n }\n\n .popover-title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs);\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .popover-close-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 2px;\n font-size: 11px;\n }\n\n .popover-body[_ngcontent-%COMP%] {\n padding: 8px 12px;\n max-height: 280px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .lineage-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .group-label[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n }\n\n .lineage-items-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .lineage-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n }\n\n .item-icon[_ngcontent-%COMP%] {\n font-size: 11px;\n margin-top: 3px;\n }\n\n .ancestor-icon[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n }\n\n .clone-icon[_ngcontent-%COMP%] {\n color: var(--mj-status-success-text);\n }\n\n .item-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n }\n\n .item-link[_ngcontent-%COMP%] {\n background: none;\n border: none;\n padding: 0;\n margin: 0;\n text-align: left;\n font-size: var(--mj-text-xs);\n font-weight: 500;\n color: var(--mj-brand-primary);\n cursor: pointer;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .item-link[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n }\n\n .item-meta[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n }"], changeDetection: 0 });
300
+ }
301
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CloneLineageChipComponent, [{
302
+ type: Component,
303
+ args: [{ standalone: true, selector: 'mj-clone-lineage-chip', template: `
304
+ @if (HasLineage) {
305
+ <div class="lineage-wrapper" (keydown.escape)="OnEscape($event)">
306
+ <!-- Compact Chip Button -->
307
+ <button
308
+ #chipTrigger
309
+ type="button"
310
+ class="lineage-chip-btn"
311
+ [class.active]="IsOpen"
312
+ [attr.aria-expanded]="IsOpen"
313
+ [attr.aria-controls]="PopoverId"
314
+ (click)="TogglePopover()"
315
+ [title]="ChipTooltip">
316
+ <i [class]="ChipIcon"></i>
317
+ <span class="chip-label">{{ChipLabel}}</span>
318
+ </button>
319
+
320
+ <!-- Lineage Popover Menu -->
321
+ @if (IsOpen) {
322
+ <!-- A disclosure panel, not a modal dialog: the page stays usable around it. -->
323
+ <div class="lineage-popover" [id]="PopoverId" role="region" aria-label="Record lineage">
324
+ <div class="popover-header">
325
+ <span class="popover-title">
326
+ <i class="fa-solid fa-code-fork"></i>
327
+ Record Clone Lineage
328
+ </span>
329
+ <button
330
+ #closeButton
331
+ type="button"
332
+ class="popover-close-btn"
333
+ (click)="ClosePopover(true)"
334
+ aria-label="Close lineage"
335
+ title="Close">
336
+ <i class="fa-solid fa-xmark"></i>
337
+ </button>
338
+ </div>
339
+
340
+ <div class="popover-body">
341
+ <!-- Ancestors Origin Section -->
342
+ @if (Ancestors && Ancestors.length > 0) {
343
+ <div class="lineage-group">
344
+ <span class="group-label">Ancestry Chain (Origins)</span>
345
+ <div class="lineage-items-list">
346
+ @for (item of Ancestors; track item.CloneLogID) {
347
+ <div class="lineage-item">
348
+ <i class="fa-solid fa-arrow-turn-up item-icon ancestor-icon"></i>
349
+ <div class="item-content">
350
+ <button
351
+ type="button"
352
+ class="item-link"
353
+ (click)="OnNavigate(item)">
354
+ {{item.DisplayName || item.RecordID}}
355
+ </button>
356
+ <span class="item-meta">
357
+ {{item.EntityName}}
358
+ @if (item.ClonedBy) {
359
+ • by {{item.ClonedBy}}
360
+ }
361
+ </span>
362
+ </div>
363
+ </div>
364
+ }
365
+ </div>
366
+ </div>
367
+ }
368
+
369
+ <!-- Direct Source Section if no full ancestry -->
370
+ @if (SourceRecord && (!Ancestors || Ancestors.length === 0)) {
371
+ <div class="lineage-group">
372
+ <span class="group-label">Cloned From</span>
373
+ <div class="lineage-item">
374
+ <i class="fa-solid fa-arrow-turn-up item-icon ancestor-icon"></i>
375
+ <div class="item-content">
376
+ <button
377
+ type="button"
378
+ class="item-link"
379
+ (click)="OnNavigate(SourceRecord)">
380
+ {{SourceRecord.DisplayName || SourceRecord.RecordID}}
381
+ </button>
382
+ <span class="item-meta">
383
+ {{SourceRecord.EntityName}}
384
+ @if (SourceRecord.ClonedBy) {
385
+ • by {{SourceRecord.ClonedBy}}
386
+ }
387
+ </span>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ }
392
+
393
+ <!-- Clones Created from This Record -->
394
+ @if (Clones && Clones.length > 0) {
395
+ <div class="lineage-group">
396
+ <span class="group-label">
397
+ Clones Created from This Record ({{Clones.length}})
398
+ </span>
399
+ <div class="lineage-items-list">
400
+ @for (clone of Clones; track clone.CloneLogID) {
401
+ <div class="lineage-item">
402
+ <i class="fa-solid fa-clone item-icon clone-icon"></i>
403
+ <div class="item-content">
404
+ <button
405
+ type="button"
406
+ class="item-link"
407
+ (click)="OnNavigate(clone)">
408
+ {{clone.DisplayName || clone.RecordID}}
409
+ </button>
410
+ <span class="item-meta">
411
+ {{clone.EntityName}}
412
+ @if (clone.ClonedBy) {
413
+ • by {{clone.ClonedBy}}
414
+ }
415
+ </span>
416
+ </div>
417
+ </div>
418
+ }
419
+ </div>
420
+ </div>
421
+ }
422
+ </div>
423
+ </div>
424
+ }
425
+ </div>
426
+ }
427
+ `, imports: [
428
+ CommonModule,
429
+ ], changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n .lineage-wrapper {\n position: relative;\n display: inline-flex;\n }\n\n .lineage-chip-btn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 8px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n font-size: var(--mj-text-xs);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease-in-out;\n user-select: none;\n }\n\n .lineage-chip-btn:hover, .lineage-chip-btn.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n }\n\n .chip-label {\n font-weight: 500;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .lineage-popover {\n position: absolute;\n top: 100%;\n left: 0;\n margin-top: 4px;\n width: 280px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n }\n\n .popover-title {\n font-size: var(--mj-text-xs);\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .popover-close-btn {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 2px;\n font-size: 11px;\n }\n\n .popover-body {\n padding: 8px 12px;\n max-height: 280px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .lineage-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .group-label {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n }\n\n .lineage-items-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .lineage-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n }\n\n .item-icon {\n font-size: 11px;\n margin-top: 3px;\n }\n\n .ancestor-icon {\n color: var(--mj-brand-primary);\n }\n\n .clone-icon {\n color: var(--mj-status-success-text);\n }\n\n .item-content {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n }\n\n .item-link {\n background: none;\n border: none;\n padding: 0;\n margin: 0;\n text-align: left;\n font-size: var(--mj-text-xs);\n font-weight: 500;\n color: var(--mj-brand-primary);\n cursor: pointer;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .item-link:hover {\n text-decoration: underline;\n }\n\n .item-meta {\n font-size: 10px;\n color: var(--mj-text-muted);\n }\n "] }]
430
+ }], null, { EntityName: [{
431
+ type: Input
432
+ }], RecordKey: [{
433
+ type: Input
434
+ }], AutoLoad: [{
435
+ type: Input
436
+ }], LineageData: [{
437
+ type: Input
438
+ }], NavigateToRecord: [{
439
+ type: Output
440
+ }], chipTrigger: [{
441
+ type: ViewChild,
442
+ args: ['chipTrigger']
443
+ }], closeButton: [{
444
+ type: ViewChild,
445
+ args: ['closeButton']
446
+ }] }); })();
447
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CloneLineageChipComponent, { className: "CloneLineageChipComponent", filePath: "src/lib/clone-lineage-chip.component.ts", lineNumber: 323 }); })();
448
+ //# sourceMappingURL=clone-lineage-chip.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-lineage-chip.component.js","sourceRoot":"","sources":["../../src/lib/clone-lineage-chip.component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Compact lineage chip and history popover component.\n *\n * Implements §12.3 of the Record Cloning architectural blueprint. Surfaces origin\n * and clone lineage metadata (\"Cloned from <name>\" or \"<n> clones\"). Clicking opens\n * a popover listing ancestral origins and descendant clones with safe navigation links.\n */\n\nimport {\n Component,\n ChangeDetectionStrategy,\n Input,\n Output,\n EventEmitter,\n OnInit,\n ChangeDetectorRef,\n ElementRef,\n ViewChild,\n inject,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport type {\n RecordCloneKey,\n RecordCloneGetLineageOutput,\n RecordCloneLineageItem,\n} from '@memberjunction/core-entities';\nimport { CompositeKey } from '@memberjunction/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { RecordCloneService } from './record-clone.service';\nimport { CompositeKeyToRecordCloneKey, type CloneNavigationEvent } from './record-clone-types';\n\n@Component({\n standalone: true,\n selector: 'mj-clone-lineage-chip',\n template: `\n @if (HasLineage) {\n <div class=\"lineage-wrapper\" (keydown.escape)=\"OnEscape($event)\">\n <!-- Compact Chip Button -->\n <button\n #chipTrigger\n type=\"button\"\n class=\"lineage-chip-btn\"\n [class.active]=\"IsOpen\"\n [attr.aria-expanded]=\"IsOpen\"\n [attr.aria-controls]=\"PopoverId\"\n (click)=\"TogglePopover()\"\n [title]=\"ChipTooltip\">\n <i [class]=\"ChipIcon\"></i>\n <span class=\"chip-label\">{{ChipLabel}}</span>\n </button>\n\n <!-- Lineage Popover Menu -->\n @if (IsOpen) {\n <!-- A disclosure panel, not a modal dialog: the page stays usable around it. -->\n <div class=\"lineage-popover\" [id]=\"PopoverId\" role=\"region\" aria-label=\"Record lineage\">\n <div class=\"popover-header\">\n <span class=\"popover-title\">\n <i class=\"fa-solid fa-code-fork\"></i>\n Record Clone Lineage\n </span>\n <button\n #closeButton\n type=\"button\"\n class=\"popover-close-btn\"\n (click)=\"ClosePopover(true)\"\n aria-label=\"Close lineage\"\n title=\"Close\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <div class=\"popover-body\">\n <!-- Ancestors Origin Section -->\n @if (Ancestors && Ancestors.length > 0) {\n <div class=\"lineage-group\">\n <span class=\"group-label\">Ancestry Chain (Origins)</span>\n <div class=\"lineage-items-list\">\n @for (item of Ancestors; track item.CloneLogID) {\n <div class=\"lineage-item\">\n <i class=\"fa-solid fa-arrow-turn-up item-icon ancestor-icon\"></i>\n <div class=\"item-content\">\n <button\n type=\"button\"\n class=\"item-link\"\n (click)=\"OnNavigate(item)\">\n {{item.DisplayName || item.RecordID}}\n </button>\n <span class=\"item-meta\">\n {{item.EntityName}}\n @if (item.ClonedBy) {\n • by {{item.ClonedBy}}\n }\n </span>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Direct Source Section if no full ancestry -->\n @if (SourceRecord && (!Ancestors || Ancestors.length === 0)) {\n <div class=\"lineage-group\">\n <span class=\"group-label\">Cloned From</span>\n <div class=\"lineage-item\">\n <i class=\"fa-solid fa-arrow-turn-up item-icon ancestor-icon\"></i>\n <div class=\"item-content\">\n <button\n type=\"button\"\n class=\"item-link\"\n (click)=\"OnNavigate(SourceRecord)\">\n {{SourceRecord.DisplayName || SourceRecord.RecordID}}\n </button>\n <span class=\"item-meta\">\n {{SourceRecord.EntityName}}\n @if (SourceRecord.ClonedBy) {\n • by {{SourceRecord.ClonedBy}}\n }\n </span>\n </div>\n </div>\n </div>\n }\n\n <!-- Clones Created from This Record -->\n @if (Clones && Clones.length > 0) {\n <div class=\"lineage-group\">\n <span class=\"group-label\">\n Clones Created from This Record ({{Clones.length}})\n </span>\n <div class=\"lineage-items-list\">\n @for (clone of Clones; track clone.CloneLogID) {\n <div class=\"lineage-item\">\n <i class=\"fa-solid fa-clone item-icon clone-icon\"></i>\n <div class=\"item-content\">\n <button\n type=\"button\"\n class=\"item-link\"\n (click)=\"OnNavigate(clone)\">\n {{clone.DisplayName || clone.RecordID}}\n </button>\n <span class=\"item-meta\">\n {{clone.EntityName}}\n @if (clone.ClonedBy) {\n • by {{clone.ClonedBy}}\n }\n </span>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n `,\n styles: [`\n .lineage-wrapper {\n position: relative;\n display: inline-flex;\n }\n\n .lineage-chip-btn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 8px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n font-size: var(--mj-text-xs);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease-in-out;\n user-select: none;\n }\n\n .lineage-chip-btn:hover, .lineage-chip-btn.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n }\n\n .chip-label {\n font-weight: 500;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .lineage-popover {\n position: absolute;\n top: 100%;\n left: 0;\n margin-top: 4px;\n width: 280px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n }\n\n .popover-title {\n font-size: var(--mj-text-xs);\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .popover-close-btn {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 2px;\n font-size: 11px;\n }\n\n .popover-body {\n padding: 8px 12px;\n max-height: 280px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .lineage-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .group-label {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n }\n\n .lineage-items-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .lineage-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n }\n\n .item-icon {\n font-size: 11px;\n margin-top: 3px;\n }\n\n .ancestor-icon {\n color: var(--mj-brand-primary);\n }\n\n .clone-icon {\n color: var(--mj-status-success-text);\n }\n\n .item-content {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n }\n\n .item-link {\n background: none;\n border: none;\n padding: 0;\n margin: 0;\n text-align: left;\n font-size: var(--mj-text-xs);\n font-weight: 500;\n color: var(--mj-brand-primary);\n cursor: pointer;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .item-link:hover {\n text-decoration: underline;\n }\n\n .item-meta {\n font-size: 10px;\n color: var(--mj-text-muted);\n }\n `],\n imports: [\n CommonModule,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CloneLineageChipComponent extends BaseAngularComponent implements OnInit {\n private cloneService = inject(RecordCloneService);\n private cdr = inject(ChangeDetectorRef);\n\n /** Entity of the record whose lineage to show. */\n @Input() EntityName = '';\n /** Key of the record: a RecordCloneKey or a record-id string (`CompositeKey.FromURLSegment` form). */\n @Input() RecordKey: RecordCloneKey | string = '';\n /** Load lineage on init. Set false and call `LoadLineage()` to control timing. */\n @Input() AutoLoad = true;\n /** Pre-loaded lineage; skips the server call when set. */\n @Input() LineageData: RecordCloneGetLineageOutput | null = null;\n\n /** Asks the host to open an ancestor or clone from the popover. */\n @Output() NavigateToRecord = new EventEmitter<CloneNavigationEvent>();\n\n public IsOpen = false;\n public IsLoading = false;\n\n public get SourceRecord(): RecordCloneLineageItem | undefined {\n if (!this.LineageData?.Ancestors || this.LineageData.Ancestors.length === 0) return undefined;\n return this.LineageData.Ancestors[this.LineageData.Ancestors.length - 1];\n }\n\n public get Ancestors(): RecordCloneLineageItem[] | undefined {\n return this.LineageData?.Ancestors;\n }\n\n public get Clones(): RecordCloneLineageItem[] | undefined {\n return this.LineageData?.Clones;\n }\n\n public get HasLineage(): boolean {\n return !!this.SourceRecord || (!!this.Clones && this.Clones.length > 0);\n }\n\n public get ChipIcon(): string {\n if (this.SourceRecord) {\n return 'fa-solid fa-code-branch';\n }\n return 'fa-solid fa-clone';\n }\n\n public get ChipLabel(): string {\n if (this.SourceRecord) {\n return `Cloned from ${this.SourceRecord.DisplayName || this.SourceRecord.RecordID}`;\n }\n if (this.Clones && this.Clones.length > 0) {\n return `${this.Clones.length} clone${this.Clones.length === 1 ? '' : 's'}`;\n }\n return 'Lineage';\n }\n\n public get ChipTooltip(): string {\n if (this.SourceRecord) {\n return `View clone origin and lineage history`;\n }\n return `View records cloned from this record`;\n }\n\n public async ngOnInit(): Promise<void> {\n if (this.AutoLoad && !this.LineageData && this.EntityName && this.RecordKey) {\n await this.LoadLineage();\n }\n }\n\n public async LoadLineage(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n try {\n const key: RecordCloneKey = typeof this.RecordKey === 'string'\n ? CompositeKeyToRecordCloneKey(\n CompositeKey.FromURLSegment(this.ProviderToUse?.EntityByName(this.EntityName), this.RecordKey)\n )\n : this.RecordKey;\n\n this.LineageData = await this.cloneService.GetLineage(\n {\n EntityName: this.EntityName,\n Key: key,\n Direction: 'both',\n },\n this.ProviderToUse\n );\n } catch {\n this.LineageData = null;\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n private static nextId = 0;\n /** Ties the chip's aria-controls to its popover. */\n public readonly PopoverId = `mj-clone-lineage-${++CloneLineageChipComponent.nextId}`;\n @ViewChild('chipTrigger') private chipTrigger?: ElementRef<HTMLButtonElement>;\n @ViewChild('closeButton') private closeButton?: ElementRef<HTMLButtonElement>;\n\n public TogglePopover(): void {\n this.IsOpen = !this.IsOpen;\n this.cdr.detectChanges();\n // Move focus into the panel on open, so keyboard users land in it.\n if (this.IsOpen) this.closeButton?.nativeElement.focus();\n }\n\n /** Closes the popover; `returnFocus` puts focus back on the chip (Escape, the close button). */\n public ClosePopover(returnFocus = false): void {\n this.IsOpen = false;\n this.cdr.markForCheck();\n if (returnFocus) this.chipTrigger?.nativeElement.focus();\n }\n\n public OnEscape(event: Event): void {\n if (!this.IsOpen) return;\n event.stopPropagation();\n this.ClosePopover(true);\n }\n\n public OnNavigate(item: RecordCloneLineageItem): void {\n // Back to the chip: the host may open the record in another tab, leaving this page as it was.\n this.ClosePopover(true);\n this.NavigateToRecord.emit({\n Kind: 'record',\n EntityName: item.EntityName,\n RecordKey: item.RecordID,\n });\n }\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Expandable hierarchical tree rendering a RecordClonePlan.
3
+ *
4
+ * Implements §12.3 & §12.4 of the Record Cloning architectural blueprint. Renders the
5
+ * planned record graph as an interactive tree with per-edge policy pills (Deep/Reference/Skip)
6
+ * with lock tooltips, per-node action badges, entity counts summary, and live search.
7
+ */
8
+ import { EventEmitter } from '@angular/core';
9
+ import type { RecordClonePlanDetails, RecordClonePlanNode, RecordClonePlanEdge } from '@memberjunction/core-entities';
10
+ import type { CloneTreeNodeViewModel, CloneEdgePolicyChange } from './record-clone-types';
11
+ import * as i0 from "@angular/core";
12
+ export declare class ClonePlanTreeComponent {
13
+ private _plan;
14
+ private cdr;
15
+ /** The plan to render. Setting it rebuilds the tree. */
16
+ set Plan(value: RecordClonePlanDetails | null);
17
+ get Plan(): RecordClonePlanDetails | null;
18
+ /** Key of the node to highlight, e.g. when the review step links back to it. */
19
+ SelectedNodeKey: string | null;
20
+ /** Let the user switch unlocked Deep branches to Skip (narrowing). Default false (read-only tree). */
21
+ AllowNarrowing: boolean;
22
+ /** Also let the user switch Skip branches to Deep (widening; needs Clone Records: Override Scope). */
23
+ AllowWidening: boolean;
24
+ /** Fires when the user selects a node row. */
25
+ NodeSelected: EventEmitter<RecordClonePlanNode>;
26
+ /** Fires when the user switches a branch's policy; the host re-plans with it as an edge override. */
27
+ EdgePolicyChanged: EventEmitter<CloneEdgePolicyChange>;
28
+ SearchTerm: string;
29
+ TreeNodes: CloneTreeNodeViewModel[];
30
+ FlatVisibleNodes: CloneTreeNodeViewModel[];
31
+ get ReferenceCount(): number;
32
+ /** Blocked only because prompted values haven't been entered yet: not a problem on the Scope step. */
33
+ get NeedsValuesOnly(): boolean;
34
+ get SkipCount(): number;
35
+ OnSearchTermChange(term: string): void;
36
+ OnSelectNode(node: RecordClonePlanNode): void;
37
+ OnToggleExpand(item: CloneTreeNodeViewModel, event: MouseEvent): void;
38
+ ExpandAll(): void;
39
+ CollapseAll(): void;
40
+ /** The policy a click on this edge's pill would switch to, or null when the pill is read-only. */
41
+ NextPolicy(edge: RecordClonePlanEdge): 'Deep' | 'Skip' | null;
42
+ OnPolicyToggle(edge: RecordClonePlanEdge, policy: 'Deep' | 'Skip', event: MouseEvent): void;
43
+ GetEdgeTooltip(edge: RecordClonePlanEdge): string;
44
+ private setAllExpanded;
45
+ private rebuildTree;
46
+ private updateVisibleNodes;
47
+ private subtreeMatches;
48
+ static ɵfac: i0.ɵɵFactoryDeclaration<ClonePlanTreeComponent, never>;
49
+ static ɵcmp: i0.ɵɵComponentDeclaration<ClonePlanTreeComponent, "mj-clone-plan-tree", never, { "Plan": { "alias": "Plan"; "required": false; }; "SelectedNodeKey": { "alias": "SelectedNodeKey"; "required": false; }; "AllowNarrowing": { "alias": "AllowNarrowing"; "required": false; }; "AllowWidening": { "alias": "AllowWidening"; "required": false; }; }, { "NodeSelected": "NodeSelected"; "EdgePolicyChanged": "EdgePolicyChanged"; }, never, never, true, never>;
50
+ }
51
+ //# sourceMappingURL=clone-plan-tree.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-plan-tree.component.d.ts","sourceRoot":"","sources":["../../src/lib/clone-plan-tree.component.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAKH,YAAY,EAGf,MAAM,eAAe,CAAC;AAIvB,OAAO,KAAK,EACR,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;;AAE1F,qBA2da,sBAAsB;IAC/B,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,GAAG,CAA6B;IAExC,wDAAwD;IACxD,IACI,IAAI,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,EAG5C;IACD,IAAI,IAAI,IAAI,sBAAsB,GAAG,IAAI,CAExC;IAED,gFAAgF;IACvE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/C,sGAAsG;IAC7F,cAAc,UAAS;IAEhC,sGAAsG;IAC7F,aAAa,UAAS;IAE/B,8CAA8C;IACpC,YAAY,oCAA2C;IAEjE,qGAAqG;IAC3F,iBAAiB,sCAA6C;IAEjE,UAAU,SAAM;IAChB,SAAS,EAAE,sBAAsB,EAAE,CAAM;IACzC,gBAAgB,EAAE,sBAAsB,EAAE,CAAM;IAEvD,IAAW,cAAc,IAAI,MAAM,CAGlC;IAED,sGAAsG;IACtG,IAAW,eAAe,IAAI,OAAO,CAGpC;IAED,IAAW,SAAS,IAAI,MAAM,CAG7B;IAEM,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI;IAM7C,cAAc,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAOrE,SAAS,IAAI,IAAI;IAMjB,WAAW,IAAI,IAAI;IAM1B,kGAAkG;IAC3F,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;IAQ7D,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAM3F,cAAc,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM;IAOxD,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,cAAc;yCApLb,sBAAsB;2CAAtB,sBAAsB;CAiMlC"}