@memberjunction/ng-list-management 5.34.1 → 5.36.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 (63) hide show
  1. package/README.md +179 -85
  2. package/dist/lib/components/audience-source-picker/audience-source-picker.component.d.ts +74 -0
  3. package/dist/lib/components/audience-source-picker/audience-source-picker.component.d.ts.map +1 -0
  4. package/dist/lib/components/audience-source-picker/audience-source-picker.component.js +396 -0
  5. package/dist/lib/components/audience-source-picker/audience-source-picker.component.js.map +1 -0
  6. package/dist/lib/components/audience-source-summary/audience-source-summary.component.d.ts +34 -0
  7. package/dist/lib/components/audience-source-summary/audience-source-summary.component.d.ts.map +1 -0
  8. package/dist/lib/components/audience-source-summary/audience-source-summary.component.js +136 -0
  9. package/dist/lib/components/audience-source-summary/audience-source-summary.component.js.map +1 -0
  10. package/dist/lib/components/list-audit-log/list-audit-log.component.d.ts +55 -0
  11. package/dist/lib/components/list-audit-log/list-audit-log.component.d.ts.map +1 -0
  12. package/dist/lib/components/list-audit-log/list-audit-log.component.js +280 -0
  13. package/dist/lib/components/list-audit-log/list-audit-log.component.js.map +1 -0
  14. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.d.ts +64 -0
  15. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.d.ts.map +1 -0
  16. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js +323 -0
  17. package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js.map +1 -0
  18. package/dist/lib/components/list-invitations/list-invitations.component.d.ts +60 -0
  19. package/dist/lib/components/list-invitations/list-invitations.component.d.ts.map +1 -0
  20. package/dist/lib/components/list-invitations/list-invitations.component.js +367 -0
  21. package/dist/lib/components/list-invitations/list-invitations.component.js.map +1 -0
  22. package/dist/lib/components/list-management-dialog/list-management-dialog.component.d.ts.map +1 -1
  23. package/dist/lib/components/list-management-dialog/list-management-dialog.component.js +0 -4
  24. package/dist/lib/components/list-management-dialog/list-management-dialog.component.js.map +1 -1
  25. package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts +18 -1
  26. package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts.map +1 -1
  27. package/dist/lib/components/list-share-dialog/list-share-dialog.component.js +115 -66
  28. package/dist/lib/components/list-share-dialog/list-share-dialog.component.js.map +1 -1
  29. package/dist/lib/components/list-stats/list-stats.component.d.ts +52 -0
  30. package/dist/lib/components/list-stats/list-stats.component.d.ts.map +1 -0
  31. package/dist/lib/components/list-stats/list-stats.component.js +283 -0
  32. package/dist/lib/components/list-stats/list-stats.component.js.map +1 -0
  33. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.d.ts +68 -0
  34. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.d.ts.map +1 -0
  35. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js +316 -0
  36. package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js.map +1 -0
  37. package/dist/lib/components/shared-with-me/shared-with-me.component.d.ts +47 -0
  38. package/dist/lib/components/shared-with-me/shared-with-me.component.d.ts.map +1 -0
  39. package/dist/lib/components/shared-with-me/shared-with-me.component.js +231 -0
  40. package/dist/lib/components/shared-with-me/shared-with-me.component.js.map +1 -0
  41. package/dist/lib/components/tag-chips/tag-chips.component.d.ts +85 -0
  42. package/dist/lib/components/tag-chips/tag-chips.component.d.ts.map +1 -0
  43. package/dist/lib/components/tag-chips/tag-chips.component.js +392 -0
  44. package/dist/lib/components/tag-chips/tag-chips.component.js.map +1 -0
  45. package/dist/lib/models/list-sharing.models.d.ts +5 -0
  46. package/dist/lib/models/list-sharing.models.d.ts.map +1 -1
  47. package/dist/lib/models/list-sharing.models.js.map +1 -1
  48. package/dist/lib/module.d.ts +17 -8
  49. package/dist/lib/module.d.ts.map +1 -1
  50. package/dist/lib/module.js +51 -6
  51. package/dist/lib/module.js.map +1 -1
  52. package/dist/lib/services/list-management.service.d.ts.map +1 -1
  53. package/dist/lib/services/list-management.service.js +0 -9
  54. package/dist/lib/services/list-management.service.js.map +1 -1
  55. package/dist/lib/services/list-sharing.service.d.ts +8 -3
  56. package/dist/lib/services/list-sharing.service.d.ts.map +1 -1
  57. package/dist/lib/services/list-sharing.service.js +33 -15
  58. package/dist/lib/services/list-sharing.service.js.map +1 -1
  59. package/dist/public-api.d.ts +9 -0
  60. package/dist/public-api.d.ts.map +1 -1
  61. package/dist/public-api.js +9 -0
  62. package/dist/public-api.js.map +1 -1
  63. package/package.json +13 -11
@@ -0,0 +1,392 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, inject } from '@angular/core';
2
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
+ import { LogError, Metadata, RunView } from '@memberjunction/core';
4
+ import * as i0 from "@angular/core";
5
+ import * as i1 from "@angular/forms";
6
+ const _forTrack0 = ($index, $item) => $item.TaggedItemID;
7
+ const _forTrack1 = ($index, $item) => $item.ID;
8
+ function TagChipsComponent_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
9
+ const _r4 = i0.ɵɵgetCurrentView();
10
+ i0.ɵɵelementStart(0, "i", 8);
11
+ i0.ɵɵlistener("click", function TagChipsComponent_For_2_Conditional_4_Template_i_click_0_listener($event) { i0.ɵɵrestoreView(_r4); const tag_r2 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnRemove(tag_r2, $event)); });
12
+ i0.ɵɵelementEnd();
13
+ } }
14
+ function TagChipsComponent_For_2_Template(rf, ctx) { if (rf & 1) {
15
+ const _r1 = i0.ɵɵgetCurrentView();
16
+ i0.ɵɵelementStart(0, "span", 4);
17
+ i0.ɵɵlistener("click", function TagChipsComponent_For_2_Template_span_click_0_listener() { const tag_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnTagClick(tag_r2)); });
18
+ i0.ɵɵelement(1, "i", 5);
19
+ i0.ɵɵelementStart(2, "span", 6);
20
+ i0.ɵɵtext(3);
21
+ i0.ɵɵelementEnd();
22
+ i0.ɵɵconditionalCreate(4, TagChipsComponent_For_2_Conditional_4_Template, 1, 0, "i", 7);
23
+ i0.ɵɵelementEnd();
24
+ } if (rf & 2) {
25
+ const tag_r2 = ctx.$implicit;
26
+ const ctx_r2 = i0.ɵɵnextContext();
27
+ i0.ɵɵadvance(3);
28
+ i0.ɵɵtextInterpolate(tag_r2.Name);
29
+ i0.ɵɵadvance();
30
+ i0.ɵɵconditional(ctx_r2.Editable ? 4 : -1);
31
+ } }
32
+ function TagChipsComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
33
+ i0.ɵɵelementStart(0, "span", 2);
34
+ i0.ɵɵtext(1);
35
+ i0.ɵɵelementEnd();
36
+ } if (rf & 2) {
37
+ const ctx_r2 = i0.ɵɵnextContext();
38
+ i0.ɵɵadvance();
39
+ i0.ɵɵtextInterpolate1("+", ctx_r2.extraTagCount, " more");
40
+ } }
41
+ function TagChipsComponent_Conditional_4_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
42
+ const _r6 = i0.ɵɵgetCurrentView();
43
+ i0.ɵɵelementStart(0, "button", 13);
44
+ i0.ɵɵlistener("click", function TagChipsComponent_Conditional_4_Conditional_2_For_2_Template_button_click_0_listener() { const s_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OnAddTag(s_r7)); });
45
+ i0.ɵɵelement(1, "i", 5);
46
+ i0.ɵɵtext(2);
47
+ i0.ɵɵelementEnd();
48
+ } if (rf & 2) {
49
+ const s_r7 = ctx.$implicit;
50
+ i0.ɵɵadvance(2);
51
+ i0.ɵɵtextInterpolate1(" ", s_r7.Name, " ");
52
+ } }
53
+ function TagChipsComponent_Conditional_4_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
54
+ const _r8 = i0.ɵɵgetCurrentView();
55
+ i0.ɵɵelementStart(0, "button", 14);
56
+ i0.ɵɵlistener("click", function TagChipsComponent_Conditional_4_Conditional_2_Conditional_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OnCreateNew()); });
57
+ i0.ɵɵelement(1, "i", 15);
58
+ i0.ɵɵtext(2);
59
+ i0.ɵɵelementEnd();
60
+ } if (rf & 2) {
61
+ const ctx_r2 = i0.ɵɵnextContext(3);
62
+ i0.ɵɵadvance(2);
63
+ i0.ɵɵtextInterpolate1(" Create \"", ctx_r2.addInput.trim(), "\" ");
64
+ } }
65
+ function TagChipsComponent_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
66
+ i0.ɵɵelementStart(0, "div", 10);
67
+ i0.ɵɵrepeaterCreate(1, TagChipsComponent_Conditional_4_Conditional_2_For_2_Template, 3, 1, "button", 11, _forTrack1);
68
+ i0.ɵɵconditionalCreate(3, TagChipsComponent_Conditional_4_Conditional_2_Conditional_3_Template, 3, 1, "button", 12);
69
+ i0.ɵɵelementEnd();
70
+ } if (rf & 2) {
71
+ const ctx_r2 = i0.ɵɵnextContext(2);
72
+ i0.ɵɵadvance();
73
+ i0.ɵɵrepeater(ctx_r2.suggestions);
74
+ i0.ɵɵadvance(2);
75
+ i0.ɵɵconditional(ctx_r2.canCreateNew ? 3 : -1);
76
+ } }
77
+ function TagChipsComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
78
+ const _r5 = i0.ɵɵgetCurrentView();
79
+ i0.ɵɵelementStart(0, "div", 3)(1, "input", 9);
80
+ i0.ɵɵtwoWayListener("ngModelChange", function TagChipsComponent_Conditional_4_Template_input_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.addInput, $event) || (ctx_r2.addInput = $event); return i0.ɵɵresetView($event); });
81
+ i0.ɵɵlistener("ngModelChange", function TagChipsComponent_Conditional_4_Template_input_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnAddInputChange($event)); })("keydown.enter", function TagChipsComponent_Conditional_4_Template_input_keydown_enter_1_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnAddInputSubmit($event)); });
82
+ i0.ɵɵelementEnd();
83
+ i0.ɵɵconditionalCreate(2, TagChipsComponent_Conditional_4_Conditional_2_Template, 4, 1, "div", 10);
84
+ i0.ɵɵelementEnd();
85
+ } if (rf & 2) {
86
+ const ctx_r2 = i0.ɵɵnextContext();
87
+ i0.ɵɵadvance();
88
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r2.addInput);
89
+ i0.ɵɵadvance();
90
+ i0.ɵɵconditional(ctx_r2.showSuggestions && (ctx_r2.suggestions.length > 0 || ctx_r2.canCreateNew) ? 2 : -1);
91
+ } }
92
+ export class TagChipsComponent extends BaseAngularComponent {
93
+ cdr = inject(ChangeDetectorRef);
94
+ /** Entity name (e.g. 'MJ: Lists'). Required. */
95
+ set EntityName(value) {
96
+ if (this._entityName !== value) {
97
+ this._entityName = value;
98
+ if (this.initialized && value && this._recordId)
99
+ void this.loadTags();
100
+ }
101
+ }
102
+ get EntityName() {
103
+ return this._entityName;
104
+ }
105
+ _entityName = null;
106
+ /** Record ID (the row's primary key, serialized). Required. */
107
+ set RecordID(value) {
108
+ if (this._recordId !== value) {
109
+ this._recordId = value;
110
+ if (this.initialized && value && this._entityName)
111
+ void this.loadTags();
112
+ }
113
+ }
114
+ get RecordID() {
115
+ return this._recordId;
116
+ }
117
+ _recordId = null;
118
+ /** When true, shows add input + remove buttons on chips. */
119
+ Editable = false;
120
+ /** Cap visible chips and surface "+N more". Defaults to 6 for cards. */
121
+ MaxDisplay = 6;
122
+ /** Emitted when a chip is clicked. Lets the parent drive filter-by-tag. */
123
+ TagClicked = new EventEmitter();
124
+ /** Emitted after add/remove so parents can refresh dependent data. */
125
+ TagsChanged = new EventEmitter();
126
+ tags = [];
127
+ loading = false;
128
+ addInput = '';
129
+ suggestions = [];
130
+ showSuggestions = false;
131
+ /** True when the trimmed input doesn't exactly match any existing tag —
132
+ * drives the "Create '<term>'" affordance at the top of the dropdown. */
133
+ canCreateNew = false;
134
+ initialized = false;
135
+ entityID = null;
136
+ async ngOnInit() {
137
+ this.initialized = true;
138
+ if (this._entityName && this._recordId)
139
+ await this.loadTags();
140
+ }
141
+ get displayedTags() {
142
+ return this.tags.slice(0, this.MaxDisplay);
143
+ }
144
+ get extraTagCount() {
145
+ return Math.max(0, this.tags.length - this.MaxDisplay);
146
+ }
147
+ OnTagClick(tag) {
148
+ this.TagClicked.emit({ TagID: tag.TagID, Name: tag.Name });
149
+ }
150
+ async OnRemove(tag, event) {
151
+ event.stopPropagation();
152
+ if (!this.Editable)
153
+ return;
154
+ try {
155
+ const md = this.metadata();
156
+ const entity = await md.GetEntityObject('MJ: Tagged Items', this.ProviderToUse.CurrentUser ?? undefined);
157
+ const loaded = await entity.Load(tag.TaggedItemID);
158
+ if (!loaded)
159
+ return;
160
+ const ok = await entity.Delete();
161
+ if (ok) {
162
+ this.tags = this.tags.filter((t) => t.TaggedItemID !== tag.TaggedItemID);
163
+ this.TagsChanged.emit();
164
+ this.cdr.markForCheck();
165
+ }
166
+ }
167
+ catch (e) {
168
+ LogError(`tag-chips: remove failed: ${e instanceof Error ? e.message : String(e)}`);
169
+ }
170
+ }
171
+ async OnAddInputChange(value) {
172
+ this.addInput = value;
173
+ const term = value.trim();
174
+ if (term.length < 1) {
175
+ this.suggestions = [];
176
+ this.canCreateNew = false;
177
+ this.showSuggestions = false;
178
+ this.cdr.markForCheck();
179
+ return;
180
+ }
181
+ try {
182
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
183
+ const escaped = term.replace(/'/g, "''");
184
+ const result = await rv.RunView({
185
+ EntityName: 'MJ: Tags',
186
+ ExtraFilter: `Name LIKE '%${escaped}%'`,
187
+ OrderBy: 'Name',
188
+ MaxRows: 8,
189
+ ResultType: 'entity_object',
190
+ });
191
+ const alreadyApplied = new Set(this.tags.map((t) => t.TagID));
192
+ this.suggestions = (result.Results ?? []).filter((t) => !alreadyApplied.has(t.ID));
193
+ // Offer "Create '<term>'" when the typed text doesn't exactly
194
+ // match any existing tag (case-insensitive). Without this, typing
195
+ // a brand-new tag name and hitting Enter does nothing — there's
196
+ // no suggestion to click.
197
+ const lowerTerm = term.toLowerCase();
198
+ this.canCreateNew = !this.suggestions.some((s) => s.Name.toLowerCase() === lowerTerm);
199
+ this.showSuggestions = true;
200
+ }
201
+ catch (e) {
202
+ LogError(`tag-chips: suggestion lookup failed: ${e instanceof Error ? e.message : String(e)}`);
203
+ }
204
+ finally {
205
+ this.cdr.markForCheck();
206
+ }
207
+ }
208
+ /**
209
+ * Enter-key handler on the input. If suggestions are showing,
210
+ * picks the first one (existing tag); otherwise creates a new tag
211
+ * from the typed text and applies it. Closes the suggestion menu
212
+ * on either path.
213
+ */
214
+ async OnAddInputSubmit(event) {
215
+ event?.preventDefault();
216
+ const term = this.addInput.trim();
217
+ if (term.length === 0)
218
+ return;
219
+ // Existing tag in suggestions takes priority — Enter selects the
220
+ // first match (matching most autocomplete UIs).
221
+ if (this.suggestions.length > 0) {
222
+ await this.OnAddTag(this.suggestions[0]);
223
+ return;
224
+ }
225
+ if (this.canCreateNew) {
226
+ await this.CreateAndAddTag(term);
227
+ }
228
+ }
229
+ async OnAddTag(tag) {
230
+ if (!this._entityName || !this._recordId)
231
+ return;
232
+ await this.applyTagToRecord(tag.ID, tag.Name);
233
+ }
234
+ /** Click handler for the "Create '<term>'" affordance. Creates the
235
+ * `MJ: Tags` row then attaches it via the standard apply path. */
236
+ async OnCreateNew() {
237
+ const term = this.addInput.trim();
238
+ if (term.length === 0)
239
+ return;
240
+ await this.CreateAndAddTag(term);
241
+ }
242
+ async CreateAndAddTag(name) {
243
+ if (!this._entityName || !this._recordId)
244
+ return;
245
+ try {
246
+ const md = this.metadata();
247
+ const tag = await md.GetEntityObject('MJ: Tags', this.ProviderToUse.CurrentUser ?? undefined);
248
+ tag.NewRecord();
249
+ tag.Name = name;
250
+ // MJ: Tags requires both Name (canonical) and DisplayName (UI).
251
+ // Default DisplayName to the same text the user typed — they can
252
+ // refine it later via the Tags entity editor if needed.
253
+ tag.DisplayName = name;
254
+ const ok = await tag.Save();
255
+ if (!ok) {
256
+ LogError(`tag-chips: create-tag save failed: ${tag.LatestResult?.CompleteMessage ?? 'unknown'}`);
257
+ return;
258
+ }
259
+ await this.applyTagToRecord(tag.ID, tag.Name);
260
+ }
261
+ catch (e) {
262
+ LogError(`tag-chips: create-and-add failed: ${e instanceof Error ? e.message : String(e)}`);
263
+ }
264
+ }
265
+ async applyTagToRecord(tagId, tagName) {
266
+ if (!this._entityName || !this._recordId)
267
+ return;
268
+ try {
269
+ const md = this.metadata();
270
+ const entity = await md.GetEntityObject('MJ: Tagged Items', this.ProviderToUse.CurrentUser ?? undefined);
271
+ entity.NewRecord();
272
+ entity.TagID = tagId;
273
+ entity.RecordID = this._recordId;
274
+ const entityInfo = md.EntityByName(this._entityName);
275
+ if (entityInfo)
276
+ entity.EntityID = entityInfo.ID;
277
+ entity.Weight = 1.0;
278
+ const ok = await entity.Save();
279
+ if (!ok) {
280
+ LogError(`tag-chips: attach-tag save failed: ${entity.LatestResult?.CompleteMessage ?? 'unknown'}`);
281
+ return;
282
+ }
283
+ this.tags = [...this.tags, { TaggedItemID: entity.ID, TagID: tagId, Name: tagName }];
284
+ this.addInput = '';
285
+ this.suggestions = [];
286
+ this.canCreateNew = false;
287
+ this.showSuggestions = false;
288
+ this.TagsChanged.emit();
289
+ this.cdr.markForCheck();
290
+ }
291
+ catch (e) {
292
+ LogError(`tag-chips: apply failed: ${e instanceof Error ? e.message : String(e)}`);
293
+ }
294
+ }
295
+ /**
296
+ * Bulk-load tags + their names in two RunViews — one for the join
297
+ * rows and one to resolve display names. Cheap; this typically returns
298
+ * fewer than 10 rows.
299
+ */
300
+ async loadTags() {
301
+ if (!this._entityName || !this._recordId) {
302
+ this.tags = [];
303
+ return;
304
+ }
305
+ this.loading = true;
306
+ this.cdr.markForCheck();
307
+ try {
308
+ const md = this.metadata();
309
+ const entityInfo = md.EntityByName(this._entityName);
310
+ if (!entityInfo) {
311
+ this.tags = [];
312
+ return;
313
+ }
314
+ this.entityID = entityInfo.ID;
315
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
316
+ const recordIdEscaped = this._recordId.replace(/'/g, "''");
317
+ const joinResult = await rv.RunView({
318
+ EntityName: 'MJ: Tagged Items',
319
+ ExtraFilter: `EntityID='${entityInfo.ID}' AND RecordID='${recordIdEscaped}'`,
320
+ Fields: ['ID', 'TagID'],
321
+ ResultType: 'simple',
322
+ });
323
+ if (!joinResult.Success) {
324
+ this.tags = [];
325
+ return;
326
+ }
327
+ const rows = joinResult.Results ?? [];
328
+ if (rows.length === 0) {
329
+ this.tags = [];
330
+ return;
331
+ }
332
+ const tagIds = Array.from(new Set(rows.map((r) => String(r.TagID))));
333
+ const tagsResult = await rv.RunView({
334
+ EntityName: 'MJ: Tags',
335
+ ExtraFilter: `ID IN (${tagIds.map((id) => `'${id}'`).join(',')})`,
336
+ Fields: ['ID', 'Name'],
337
+ ResultType: 'simple',
338
+ });
339
+ const nameById = new Map();
340
+ for (const t of tagsResult.Results ?? [])
341
+ nameById.set(String(t.ID), String(t.Name));
342
+ this.tags = rows
343
+ .map((r) => ({
344
+ TaggedItemID: String(r.ID),
345
+ TagID: String(r.TagID),
346
+ Name: nameById.get(String(r.TagID)) ?? '(unknown tag)',
347
+ }))
348
+ .sort((a, b) => a.Name.localeCompare(b.Name));
349
+ }
350
+ finally {
351
+ this.loading = false;
352
+ this.cdr.markForCheck();
353
+ }
354
+ }
355
+ metadata() {
356
+ return this.ProviderToUse ?? new Metadata();
357
+ }
358
+ static ɵfac = /*@__PURE__*/ (() => { let ɵTagChipsComponent_BaseFactory; return function TagChipsComponent_Factory(__ngFactoryType__) { return (ɵTagChipsComponent_BaseFactory || (ɵTagChipsComponent_BaseFactory = i0.ɵɵgetInheritedFactory(TagChipsComponent)))(__ngFactoryType__ || TagChipsComponent); }; })();
359
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TagChipsComponent, selectors: [["mj-tag-chips"]], inputs: { EntityName: "EntityName", RecordID: "RecordID", Editable: "Editable", MaxDisplay: "MaxDisplay" }, outputs: { TagClicked: "TagClicked", TagsChanged: "TagsChanged" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 4, consts: [[1, "tag-chips"], [1, "tag-chip"], [1, "tag-chip", "tag-chip--more"], [1, "tag-chip-add"], [1, "tag-chip", 3, "click"], [1, "fa-solid", "fa-tag"], [1, "tag-chip__name"], ["title", "Remove tag", 1, "fa-solid", "fa-xmark", "tag-chip__remove"], ["title", "Remove tag", 1, "fa-solid", "fa-xmark", "tag-chip__remove", 3, "click"], ["type", "text", "placeholder", "Add tag\u2026", 1, "tag-chip-add__input", 3, "ngModelChange", "keydown.enter", "ngModel"], [1, "tag-chip-add__suggestions"], ["type", "button", 1, "tag-chip-add__suggestion"], ["type", "button", "title", "Create a new tag with this name", 1, "tag-chip-add__suggestion", "tag-chip-add__suggestion--create"], ["type", "button", 1, "tag-chip-add__suggestion", 3, "click"], ["type", "button", "title", "Create a new tag with this name", 1, "tag-chip-add__suggestion", "tag-chip-add__suggestion--create", 3, "click"], [1, "fa-solid", "fa-plus"]], template: function TagChipsComponent_Template(rf, ctx) { if (rf & 1) {
360
+ i0.ɵɵelementStart(0, "div", 0);
361
+ i0.ɵɵrepeaterCreate(1, TagChipsComponent_For_2_Template, 5, 2, "span", 1, _forTrack0);
362
+ i0.ɵɵconditionalCreate(3, TagChipsComponent_Conditional_3_Template, 2, 1, "span", 2);
363
+ i0.ɵɵconditionalCreate(4, TagChipsComponent_Conditional_4_Template, 3, 2, "div", 3);
364
+ i0.ɵɵelementEnd();
365
+ } if (rf & 2) {
366
+ i0.ɵɵclassProp("tag-chips--loading", ctx.loading);
367
+ i0.ɵɵadvance();
368
+ i0.ɵɵrepeater(ctx.displayedTags);
369
+ i0.ɵɵadvance(2);
370
+ i0.ɵɵconditional(ctx.extraTagCount > 0 ? 3 : -1);
371
+ i0.ɵɵadvance();
372
+ i0.ɵɵconditional(ctx.Editable ? 4 : -1);
373
+ } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel], styles: [".tag-chips[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n}\n\n.tag-chips--loading[_ngcontent-%COMP%] {\n opacity: 0.6;\n}\n\n.tag-chip[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 11px;\n cursor: pointer;\n user-select: none;\n transition: background 0.12s ease;\n}\n\n.tag-chip[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n}\n\n.tag-chip__name[_ngcontent-%COMP%] {\n white-space: nowrap;\n}\n\n.tag-chip__remove[_ngcontent-%COMP%] {\n cursor: pointer;\n margin-left: 2px;\n opacity: 0.6;\n font-size: 10px;\n}\n\n.tag-chip__remove[_ngcontent-%COMP%]:hover {\n opacity: 1;\n color: var(--mj-status-error);\n}\n\n.tag-chip--more[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n border-color: var(--mj-border-default);\n}\n\n.tag-chip-add[_ngcontent-%COMP%] {\n position: relative;\n display: inline-block;\n}\n\n.tag-chip-add__input[_ngcontent-%COMP%] {\n padding: 3px 8px;\n border: 1px dashed var(--mj-border-default);\n border-radius: 999px;\n background: transparent;\n font-size: 11px;\n width: 110px;\n color: var(--mj-text-primary);\n}\n\n.tag-chip-add__input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-style: solid;\n border-color: var(--mj-brand-primary);\n}\n\n.tag-chip-add__suggestions[_ngcontent-%COMP%] {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 50;\n margin-top: 2px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: var(--mj-shadow-md);\n min-width: 180px;\n max-height: 200px;\n overflow-y: auto;\n}\n\n.tag-chip-add__suggestion[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n text-align: left;\n background: none;\n border: none;\n padding: 6px 10px;\n font-size: 12px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.tag-chip-add__suggestion[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n\n\n.tag-chip-add__suggestion--create[_ngcontent-%COMP%] {\n border-top: 1px solid var(--mj-border-default);\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n.tag-chip-add__suggestion--create[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}"], changeDetection: 0 });
374
+ }
375
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TagChipsComponent, [{
376
+ type: Component,
377
+ args: [{ standalone: false, selector: 'mj-tag-chips', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tag-chips\" [class.tag-chips--loading]=\"loading\">\n @for (tag of displayedTags; track tag.TaggedItemID) {\n <span class=\"tag-chip\" (click)=\"OnTagClick(tag)\">\n <i class=\"fa-solid fa-tag\"></i>\n <span class=\"tag-chip__name\">{{ tag.Name }}</span>\n @if (Editable) {\n <i\n class=\"fa-solid fa-xmark tag-chip__remove\"\n title=\"Remove tag\"\n (click)=\"OnRemove(tag, $event)\"></i>\n }\n </span>\n }\n\n @if (extraTagCount > 0) {\n <span class=\"tag-chip tag-chip--more\">+{{ extraTagCount }} more</span>\n }\n\n @if (Editable) {\n <div class=\"tag-chip-add\">\n <input\n type=\"text\"\n class=\"tag-chip-add__input\"\n [(ngModel)]=\"addInput\"\n (ngModelChange)=\"OnAddInputChange($event)\"\n (keydown.enter)=\"OnAddInputSubmit($event)\"\n placeholder=\"Add tag\u2026\" />\n @if (showSuggestions && (suggestions.length > 0 || canCreateNew)) {\n <div class=\"tag-chip-add__suggestions\">\n @for (s of suggestions; track s.ID) {\n <button class=\"tag-chip-add__suggestion\" (click)=\"OnAddTag(s)\" type=\"button\">\n <i class=\"fa-solid fa-tag\"></i>\n {{ s.Name }}\n </button>\n }\n @if (canCreateNew) {\n <button\n class=\"tag-chip-add__suggestion tag-chip-add__suggestion--create\"\n (click)=\"OnCreateNew()\"\n type=\"button\"\n title=\"Create a new tag with this name\">\n <i class=\"fa-solid fa-plus\"></i>\n Create \"{{ addInput.trim() }}\"\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".tag-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n}\n\n.tag-chips--loading {\n opacity: 0.6;\n}\n\n.tag-chip {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 11px;\n cursor: pointer;\n user-select: none;\n transition: background 0.12s ease;\n}\n\n.tag-chip:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n}\n\n.tag-chip__name {\n white-space: nowrap;\n}\n\n.tag-chip__remove {\n cursor: pointer;\n margin-left: 2px;\n opacity: 0.6;\n font-size: 10px;\n}\n\n.tag-chip__remove:hover {\n opacity: 1;\n color: var(--mj-status-error);\n}\n\n.tag-chip--more {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n border-color: var(--mj-border-default);\n}\n\n.tag-chip-add {\n position: relative;\n display: inline-block;\n}\n\n.tag-chip-add__input {\n padding: 3px 8px;\n border: 1px dashed var(--mj-border-default);\n border-radius: 999px;\n background: transparent;\n font-size: 11px;\n width: 110px;\n color: var(--mj-text-primary);\n}\n\n.tag-chip-add__input:focus {\n outline: none;\n border-style: solid;\n border-color: var(--mj-brand-primary);\n}\n\n.tag-chip-add__suggestions {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 50;\n margin-top: 2px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: var(--mj-shadow-md);\n min-width: 180px;\n max-height: 200px;\n overflow-y: auto;\n}\n\n.tag-chip-add__suggestion {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n text-align: left;\n background: none;\n border: none;\n padding: 6px 10px;\n font-size: 12px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.tag-chip-add__suggestion:hover {\n background: var(--mj-bg-surface-card);\n}\n\n/* \"Create '<term>'\" affordance \u2014 visually distinct from existing-tag suggestions */\n.tag-chip-add__suggestion--create {\n border-top: 1px solid var(--mj-border-default);\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n.tag-chip-add__suggestion--create:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n"] }]
378
+ }], null, { EntityName: [{
379
+ type: Input
380
+ }], RecordID: [{
381
+ type: Input
382
+ }], Editable: [{
383
+ type: Input
384
+ }], MaxDisplay: [{
385
+ type: Input
386
+ }], TagClicked: [{
387
+ type: Output
388
+ }], TagsChanged: [{
389
+ type: Output
390
+ }] }); })();
391
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TagChipsComponent, { className: "TagChipsComponent", filePath: "src/lib/components/tag-chips/tag-chips.component.ts", lineNumber: 35 }); })();
392
+ //# sourceMappingURL=tag-chips.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-chips.component.js","sourceRoot":"","sources":["../../../../src/lib/components/tag-chips/tag-chips.component.ts","../../../../src/lib/components/tag-chips/tag-chips.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAU,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEnI,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;;;;;;;ICG3D,4BAGkC;IAAhC,yOAAS,+BAAqB,KAAC;IAAC,iBAAI;;;;IAP1C,+BAAiD;IAA1B,oMAAS,yBAAe,KAAC;IAC9C,uBAA+B;IAC/B,+BAA6B;IAAA,YAAc;IAAA,iBAAO;IAClD,uFAAgB;IAMlB,iBAAO;;;;IAPwB,eAAc;IAAd,iCAAc;IAC3C,cAKC;IALD,0CAKC;;;IAKH,+BAAsC;IAAA,YAAyB;IAAA,iBAAO;;;IAAhC,cAAyB;IAAzB,yDAAyB;;;;IAevD,kCAA6E;IAApC,iOAAS,qBAAW,KAAC;IAC5D,uBAA+B;IAC/B,YACF;IAAA,iBAAS;;;IADP,eACF;IADE,0CACF;;;;IAGA,kCAI0C;IAFxC,kNAAS,oBAAa,KAAC;IAGvB,wBAAgC;IAChC,YACF;IAAA,iBAAS;;;IADP,eACF;IADE,kEACF;;;IAfJ,+BAAuC;IACrC,oHAKC;IACD,mHAAoB;IAUtB,iBAAM;;;IAhBJ,cAKC;IALD,iCAKC;IACD,eASC;IATD,8CASC;;;;IAxBL,AADF,8BAA0B,eAOG;IAHzB,ySAAsB;IAEtB,AADA,0MAAiB,+BAAwB,KAAC,6LACzB,+BAAwB,KAAC;IAL5C,iBAM2B;IAC3B,kGAAmE;IAoBrE,iBAAM;;;IAxBF,cAAsB;IAAtB,+CAAsB;IAIxB,cAmBC;IAnBD,2GAmBC;;ADZP,MAAM,OAAO,iBAAkB,SAAQ,oBAAoB;IACxC,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,gDAAgD;IAChD,IACI,UAAU,CAAC,KAAoB;QACjC,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,IAAI,IAAI,CAAC,WAAW,IAAI,KAAK,IAAI,IAAI,CAAC,SAAS;gBAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;IACD,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IACO,WAAW,GAAkB,IAAI,CAAC;IAE1C,+DAA+D;IAC/D,IACI,QAAQ,CAAC,KAAoB;QAC/B,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,IAAI,CAAC,WAAW,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW;gBAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC;IACH,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IACO,SAAS,GAAkB,IAAI,CAAC;IAExC,4DAA4D;IACnD,QAAQ,GAAG,KAAK,CAAC;IAE1B,wEAAwE;IAC/D,UAAU,GAAG,CAAC,CAAC;IAExB,2EAA2E;IACjE,UAAU,GAAG,IAAI,YAAY,EAAmC,CAAC;IAE3E,sEAAsE;IAC5D,WAAW,GAAG,IAAI,YAAY,EAAQ,CAAC;IAE1C,IAAI,GAAiB,EAAE,CAAC;IACxB,OAAO,GAAG,KAAK,CAAC;IAChB,QAAQ,GAAG,EAAE,CAAC;IACd,WAAW,GAAkB,EAAE,CAAC;IAChC,eAAe,GAAG,KAAK,CAAC;IAC/B;8EAC0E;IACnE,YAAY,GAAG,KAAK,CAAC;IAEpB,WAAW,GAAG,KAAK,CAAC;IACpB,QAAQ,GAAkB,IAAI,CAAC;IAEvC,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IAChE,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC;IAEM,UAAU,CAAC,GAAe;QAC/B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,GAAe,EAAE,KAAY;QACjD,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAqB,kBAAkB,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;YAC7H,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACjC,IAAI,EAAE,EAAE,CAAC;gBACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,YAAY,CAAC,CAAC;gBACzE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;gBACxB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,6BAA6B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,gBAAgB,CAAC,KAAa;QACzC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC1B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAc;gBAC3C,UAAU,EAAE,UAAU;gBACtB,WAAW,EAAE,eAAe,OAAO,IAAI;gBACvC,OAAO,EAAE,MAAM;gBACf,OAAO,EAAE,CAAC;gBACV,UAAU,EAAE,eAAe;aAC5B,CAAC,CAAC;YACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9D,IAAI,CAAC,WAAW,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACnF,8DAA8D;YAC9D,kEAAkE;YAClE,gEAAgE;YAChE,0BAA0B;YAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CACxC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,SAAS,CAC1C,CAAC;YACF,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,wCAAwC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjG,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,gBAAgB,CAAC,KAAa;QACzC,KAAK,EAAE,cAAc,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,iEAAiE;QACjE,gDAAgD;QAChD,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,GAAgB;QACpC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QACjD,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED;uEACmE;IAC5D,KAAK,CAAC,WAAW;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,IAAY;QACxC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QACjD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,eAAe,CAAc,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;YAC3G,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAChB,gEAAgE;YAChE,iEAAiE;YACjE,wDAAwD;YACxD,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC;YACvB,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,QAAQ,CAAC,sCAAsC,GAAG,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;gBACjG,OAAO;YACT,CAAC;YACD,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,qCAAqC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,KAAa,EAAE,OAAe;QAC3D,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QACjD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAqB,kBAAkB,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;YAC7H,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;YACjC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrD,IAAI,UAAU;gBAAE,MAAM,CAAC,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC;YACpB,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,QAAQ,CAAC,sCAAsC,MAAM,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;gBACpG,OAAO;YACT,CAAC;YACD,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YACrF,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC1B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,4BAA4B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,QAAQ;QACpB,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;YAE9B,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC3D,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAgC;gBACjE,UAAU,EAAE,kBAAkB;gBAC9B,WAAW,EAAE,aAAa,UAAU,CAAC,EAAE,mBAAmB,eAAe,GAAG;gBAC5E,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC;gBACvB,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACrE,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAA+B;gBAChE,UAAU,EAAE,UAAU;gBACtB,WAAW,EAAE,UAAU,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;gBACjE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;gBACtB,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC3C,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,OAAO,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACrF,IAAI,CAAC,IAAI,GAAG,IAAI;iBACb,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACX,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;gBACtB,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,eAAe;aACvD,CAAC,CAAC;iBACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,QAAQ;QACd,OAAQ,IAAI,CAAC,aAAqC,IAAI,IAAI,QAAQ,EAAE,CAAC;IACvE,CAAC;iPAjRU,iBAAiB,yBAAjB,iBAAiB;6DAAjB,iBAAiB;YClC9B,8BAA4D;YAC1D,qFAWC;YAED,oFAAyB;YAIzB,mFAAgB;YA+BlB,iBAAM;;YAjDiB,iDAAoC;YACzD,cAWC;YAXD,gCAWC;YAED,eAEC;YAFD,gDAEC;YAED,cA8BC;YA9BD,uCA8BC;;;iFDdU,iBAAiB;cAP7B,SAAS;6BACI,KAAK,YACP,cAAc,mBAGP,uBAAuB,CAAC,MAAM;;kBAM9C,KAAK;;kBAaL,KAAK;;kBAaL,KAAK;;kBAGL,KAAK;;kBAGL,MAAM;;kBAGN,MAAM;;kFAvCI,iBAAiB","sourcesContent":["import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { LogError, Metadata, RunView } from '@memberjunction/core';\nimport type { MJTagEntity, MJTaggedItemEntity } from '@memberjunction/core-entities';\n\n/**\n * Compact tag chips for a single record (mockup 22). Loads tags by\n * querying `MJ: Tagged Items` filtered to `(EntityID, RecordID)`, then\n * resolves names from `MJ: Tags` in a single batch. Render-only by\n * default; pass `Editable=true` to surface an inline add field and\n * per-chip remove buttons.\n *\n * Intentionally lightweight — not a tag manager. For richer tag tooling\n * (cloud view, related records, etc.) use `mj-record-tags` from\n * `@memberjunction/ng-record-tags`.\n */\n\ninterface DisplayTag {\n /** TaggedItem row ID — used for removal. */\n TaggedItemID: string;\n /** Tag.ID — useful for downstream filtering / lookups. */\n TagID: string;\n /** Tag display name. */\n Name: string;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-tag-chips',\n templateUrl: './tag-chips.component.html',\n styleUrls: ['./tag-chips.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TagChipsComponent extends BaseAngularComponent implements OnInit {\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Entity name (e.g. 'MJ: Lists'). Required. */\n @Input()\n set EntityName(value: string | null) {\n if (this._entityName !== value) {\n this._entityName = value;\n if (this.initialized && value && this._recordId) void this.loadTags();\n }\n }\n get EntityName(): string | null {\n return this._entityName;\n }\n private _entityName: string | null = null;\n\n /** Record ID (the row's primary key, serialized). Required. */\n @Input()\n set RecordID(value: string | null) {\n if (this._recordId !== value) {\n this._recordId = value;\n if (this.initialized && value && this._entityName) void this.loadTags();\n }\n }\n get RecordID(): string | null {\n return this._recordId;\n }\n private _recordId: string | null = null;\n\n /** When true, shows add input + remove buttons on chips. */\n @Input() Editable = false;\n\n /** Cap visible chips and surface \"+N more\". Defaults to 6 for cards. */\n @Input() MaxDisplay = 6;\n\n /** Emitted when a chip is clicked. Lets the parent drive filter-by-tag. */\n @Output() TagClicked = new EventEmitter<{ TagID: string; Name: string }>();\n\n /** Emitted after add/remove so parents can refresh dependent data. */\n @Output() TagsChanged = new EventEmitter<void>();\n\n public tags: DisplayTag[] = [];\n public loading = false;\n public addInput = '';\n public suggestions: MJTagEntity[] = [];\n public showSuggestions = false;\n /** True when the trimmed input doesn't exactly match any existing tag —\n * drives the \"Create '<term>'\" affordance at the top of the dropdown. */\n public canCreateNew = false;\n\n private initialized = false;\n private entityID: string | null = null;\n\n async ngOnInit(): Promise<void> {\n this.initialized = true;\n if (this._entityName && this._recordId) await this.loadTags();\n }\n\n public get displayedTags(): DisplayTag[] {\n return this.tags.slice(0, this.MaxDisplay);\n }\n\n public get extraTagCount(): number {\n return Math.max(0, this.tags.length - this.MaxDisplay);\n }\n\n public OnTagClick(tag: DisplayTag): void {\n this.TagClicked.emit({ TagID: tag.TagID, Name: tag.Name });\n }\n\n public async OnRemove(tag: DisplayTag, event: Event): Promise<void> {\n event.stopPropagation();\n if (!this.Editable) return;\n try {\n const md = this.metadata();\n const entity = await md.GetEntityObject<MJTaggedItemEntity>('MJ: Tagged Items', this.ProviderToUse.CurrentUser ?? undefined);\n const loaded = await entity.Load(tag.TaggedItemID);\n if (!loaded) return;\n const ok = await entity.Delete();\n if (ok) {\n this.tags = this.tags.filter((t) => t.TaggedItemID !== tag.TaggedItemID);\n this.TagsChanged.emit();\n this.cdr.markForCheck();\n }\n } catch (e) {\n LogError(`tag-chips: remove failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n public async OnAddInputChange(value: string): Promise<void> {\n this.addInput = value;\n const term = value.trim();\n if (term.length < 1) {\n this.suggestions = [];\n this.canCreateNew = false;\n this.showSuggestions = false;\n this.cdr.markForCheck();\n return;\n }\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const escaped = term.replace(/'/g, \"''\");\n const result = await rv.RunView<MJTagEntity>({\n EntityName: 'MJ: Tags',\n ExtraFilter: `Name LIKE '%${escaped}%'`,\n OrderBy: 'Name',\n MaxRows: 8,\n ResultType: 'entity_object',\n });\n const alreadyApplied = new Set(this.tags.map((t) => t.TagID));\n this.suggestions = (result.Results ?? []).filter((t) => !alreadyApplied.has(t.ID));\n // Offer \"Create '<term>'\" when the typed text doesn't exactly\n // match any existing tag (case-insensitive). Without this, typing\n // a brand-new tag name and hitting Enter does nothing — there's\n // no suggestion to click.\n const lowerTerm = term.toLowerCase();\n this.canCreateNew = !this.suggestions.some(\n (s) => s.Name.toLowerCase() === lowerTerm,\n );\n this.showSuggestions = true;\n } catch (e) {\n LogError(`tag-chips: suggestion lookup failed: ${e instanceof Error ? e.message : String(e)}`);\n } finally {\n this.cdr.markForCheck();\n }\n }\n\n /**\n * Enter-key handler on the input. If suggestions are showing,\n * picks the first one (existing tag); otherwise creates a new tag\n * from the typed text and applies it. Closes the suggestion menu\n * on either path.\n */\n public async OnAddInputSubmit(event?: Event): Promise<void> {\n event?.preventDefault();\n const term = this.addInput.trim();\n if (term.length === 0) return;\n // Existing tag in suggestions takes priority — Enter selects the\n // first match (matching most autocomplete UIs).\n if (this.suggestions.length > 0) {\n await this.OnAddTag(this.suggestions[0]);\n return;\n }\n if (this.canCreateNew) {\n await this.CreateAndAddTag(term);\n }\n }\n\n public async OnAddTag(tag: MJTagEntity): Promise<void> {\n if (!this._entityName || !this._recordId) return;\n await this.applyTagToRecord(tag.ID, tag.Name);\n }\n\n /** Click handler for the \"Create '<term>'\" affordance. Creates the\n * `MJ: Tags` row then attaches it via the standard apply path. */\n public async OnCreateNew(): Promise<void> {\n const term = this.addInput.trim();\n if (term.length === 0) return;\n await this.CreateAndAddTag(term);\n }\n\n private async CreateAndAddTag(name: string): Promise<void> {\n if (!this._entityName || !this._recordId) return;\n try {\n const md = this.metadata();\n const tag = await md.GetEntityObject<MJTagEntity>('MJ: Tags', this.ProviderToUse.CurrentUser ?? undefined);\n tag.NewRecord();\n tag.Name = name;\n // MJ: Tags requires both Name (canonical) and DisplayName (UI).\n // Default DisplayName to the same text the user typed — they can\n // refine it later via the Tags entity editor if needed.\n tag.DisplayName = name;\n const ok = await tag.Save();\n if (!ok) {\n LogError(`tag-chips: create-tag save failed: ${tag.LatestResult?.CompleteMessage ?? 'unknown'}`);\n return;\n }\n await this.applyTagToRecord(tag.ID, tag.Name);\n } catch (e) {\n LogError(`tag-chips: create-and-add failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n private async applyTagToRecord(tagId: string, tagName: string): Promise<void> {\n if (!this._entityName || !this._recordId) return;\n try {\n const md = this.metadata();\n const entity = await md.GetEntityObject<MJTaggedItemEntity>('MJ: Tagged Items', this.ProviderToUse.CurrentUser ?? undefined);\n entity.NewRecord();\n entity.TagID = tagId;\n entity.RecordID = this._recordId;\n const entityInfo = md.EntityByName(this._entityName);\n if (entityInfo) entity.EntityID = entityInfo.ID;\n entity.Weight = 1.0;\n const ok = await entity.Save();\n if (!ok) {\n LogError(`tag-chips: attach-tag save failed: ${entity.LatestResult?.CompleteMessage ?? 'unknown'}`);\n return;\n }\n this.tags = [...this.tags, { TaggedItemID: entity.ID, TagID: tagId, Name: tagName }];\n this.addInput = '';\n this.suggestions = [];\n this.canCreateNew = false;\n this.showSuggestions = false;\n this.TagsChanged.emit();\n this.cdr.markForCheck();\n } catch (e) {\n LogError(`tag-chips: apply failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Bulk-load tags + their names in two RunViews — one for the join\n * rows and one to resolve display names. Cheap; this typically returns\n * fewer than 10 rows.\n */\n private async loadTags(): Promise<void> {\n if (!this._entityName || !this._recordId) {\n this.tags = [];\n return;\n }\n this.loading = true;\n this.cdr.markForCheck();\n try {\n const md = this.metadata();\n const entityInfo = md.EntityByName(this._entityName);\n if (!entityInfo) {\n this.tags = [];\n return;\n }\n this.entityID = entityInfo.ID;\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const recordIdEscaped = this._recordId.replace(/'/g, \"''\");\n const joinResult = await rv.RunView<{ ID: string; TagID: string }>({\n EntityName: 'MJ: Tagged Items',\n ExtraFilter: `EntityID='${entityInfo.ID}' AND RecordID='${recordIdEscaped}'`,\n Fields: ['ID', 'TagID'],\n ResultType: 'simple',\n });\n if (!joinResult.Success) {\n this.tags = [];\n return;\n }\n const rows = joinResult.Results ?? [];\n if (rows.length === 0) {\n this.tags = [];\n return;\n }\n const tagIds = Array.from(new Set(rows.map((r) => String(r.TagID))));\n const tagsResult = await rv.RunView<{ ID: string; Name: string }>({\n EntityName: 'MJ: Tags',\n ExtraFilter: `ID IN (${tagIds.map((id) => `'${id}'`).join(',')})`,\n Fields: ['ID', 'Name'],\n ResultType: 'simple',\n });\n const nameById = new Map<string, string>();\n for (const t of tagsResult.Results ?? []) nameById.set(String(t.ID), String(t.Name));\n this.tags = rows\n .map((r) => ({\n TaggedItemID: String(r.ID),\n TagID: String(r.TagID),\n Name: nameById.get(String(r.TagID)) ?? '(unknown tag)',\n }))\n .sort((a, b) => a.Name.localeCompare(b.Name));\n } finally {\n this.loading = false;\n this.cdr.markForCheck();\n }\n }\n\n private metadata(): Metadata {\n return (this.ProviderToUse as unknown as Metadata) ?? new Metadata();\n }\n}\n","<div class=\"tag-chips\" [class.tag-chips--loading]=\"loading\">\n @for (tag of displayedTags; track tag.TaggedItemID) {\n <span class=\"tag-chip\" (click)=\"OnTagClick(tag)\">\n <i class=\"fa-solid fa-tag\"></i>\n <span class=\"tag-chip__name\">{{ tag.Name }}</span>\n @if (Editable) {\n <i\n class=\"fa-solid fa-xmark tag-chip__remove\"\n title=\"Remove tag\"\n (click)=\"OnRemove(tag, $event)\"></i>\n }\n </span>\n }\n\n @if (extraTagCount > 0) {\n <span class=\"tag-chip tag-chip--more\">+{{ extraTagCount }} more</span>\n }\n\n @if (Editable) {\n <div class=\"tag-chip-add\">\n <input\n type=\"text\"\n class=\"tag-chip-add__input\"\n [(ngModel)]=\"addInput\"\n (ngModelChange)=\"OnAddInputChange($event)\"\n (keydown.enter)=\"OnAddInputSubmit($event)\"\n placeholder=\"Add tag…\" />\n @if (showSuggestions && (suggestions.length > 0 || canCreateNew)) {\n <div class=\"tag-chip-add__suggestions\">\n @for (s of suggestions; track s.ID) {\n <button class=\"tag-chip-add__suggestion\" (click)=\"OnAddTag(s)\" type=\"button\">\n <i class=\"fa-solid fa-tag\"></i>\n {{ s.Name }}\n </button>\n }\n @if (canCreateNew) {\n <button\n class=\"tag-chip-add__suggestion tag-chip-add__suggestion--create\"\n (click)=\"OnCreateNew()\"\n type=\"button\"\n title=\"Create a new tag with this name\">\n <i class=\"fa-solid fa-plus\"></i>\n Create \"{{ addInput.trim() }}\"\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n"]}
@@ -189,5 +189,10 @@ export interface ListShareDialogResult {
189
189
  * Shares that were removed
190
190
  */
191
191
  sharesRemoved: string[];
192
+ /**
193
+ * Whether the user wants newly-added shares to fan out as notifications
194
+ * to the recipient (email + in-app, per their preferences). Default true.
195
+ */
196
+ notifyByEmail?: boolean;
192
197
  }
193
198
  //# sourceMappingURL=list-sharing.models.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"list-sharing.models.d.ts","sourceRoot":"","sources":["../../../src/lib/models/list-sharing.models.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,eAAe,EAAE,mBAAmB,CAAC;IAErC;;OAEG;IACH,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;IAE5C;;OAEG;IACH,cAAc,CAAC,EAAE,IAAI,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,cAAc,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAE3B;;OAEG;IACH,WAAW,EAAE,aAAa,EAAE,CAAC;IAE7B;;OAEG;IACH,aAAa,EAAE,aAAa,EAAE,CAAC;IAE/B;;OAEG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB"}
1
+ {"version":3,"file":"list-sharing.models.d.ts","sourceRoot":"","sources":["../../../src/lib/models/list-sharing.models.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,eAAe,EAAE,mBAAmB,CAAC;IAErC;;OAEG;IACH,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;IAE5C;;OAEG;IACH,cAAc,CAAC,EAAE,IAAI,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,cAAc,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAE3B;;OAEG;IACH,WAAW,EAAE,aAAa,EAAE,CAAC;IAE7B;;OAEG;IACH,aAAa,EAAE,aAAa,EAAE,CAAC;IAE/B;;OAEG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"list-sharing.models.js","sourceRoot":"","sources":["../../../src/lib/models/list-sharing.models.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Permission levels for list sharing\n * Maps to the PermissionLevel values in ResourcePermission table\n */\nexport type ListPermissionLevel = 'View' | 'Edit' | 'Owner';\n\n/**\n * Information about a list share\n */\nexport interface ListShareInfo {\n /**\n * The ResourcePermission record ID\n */\n shareId: string;\n\n /**\n * The List ID being shared\n */\n listId: string;\n\n /**\n * Type of recipient\n */\n type: 'User' | 'Role';\n\n /**\n * User or Role ID\n */\n recipientId: string;\n\n /**\n * Name of the user or role\n */\n recipientName: string;\n\n /**\n * Email (only for users)\n */\n recipientEmail?: string;\n\n /**\n * Permission level granted\n */\n permissionLevel: ListPermissionLevel;\n\n /**\n * Status of the share (Approved, Pending, Rejected)\n */\n status: 'Approved' | 'Pending' | 'Rejected';\n\n /**\n * When sharing starts (optional)\n */\n startSharingAt?: Date;\n\n /**\n * When sharing ends (optional)\n */\n endSharingAt?: Date;\n}\n\n/**\n * Result of a share operation\n */\nexport interface ListShareResult {\n /**\n * Whether the operation succeeded\n */\n success: boolean;\n\n /**\n * The share ID (ResourcePermission ID) if successful\n */\n shareId?: string;\n\n /**\n * Message describing the result\n */\n message: string;\n}\n\n/**\n * Recipient for sharing (used in autocomplete)\n */\nexport interface ShareRecipient {\n /**\n * User or Role ID\n */\n id: string;\n\n /**\n * Display name\n */\n name: string;\n\n /**\n * Email (only for users)\n */\n email?: string;\n\n /**\n * Type of recipient\n */\n type: 'User' | 'Role';\n}\n\n/**\n * Summary of sharing status for a list\n */\nexport interface ListSharingSummary {\n /**\n * List ID\n */\n listId: string;\n\n /**\n * Total number of shares\n */\n totalShares: number;\n\n /**\n * Number of user shares\n */\n userShares: number;\n\n /**\n * Number of role shares\n */\n roleShares: number;\n\n /**\n * Is this list shared with the current user (not owner)\n */\n isSharedWithMe: boolean;\n\n /**\n * Has the current user shared this list with others\n */\n isSharedByMe: boolean;\n}\n\n/**\n * Permission capabilities based on role\n */\nexport interface ListPermissions {\n /**\n * Can view the list contents\n */\n canView: boolean;\n\n /**\n * Can add/remove items from the list\n */\n canEditItems: boolean;\n\n /**\n * Can edit list properties (name, description, category)\n */\n canEditProperties: boolean;\n\n /**\n * Can share the list with others\n */\n canShare: boolean;\n\n /**\n * Can delete the list\n */\n canDelete: boolean;\n\n /**\n * The effective permission level\n */\n effectiveLevel: ListPermissionLevel | 'None';\n\n /**\n * Whether the user is the owner\n */\n isOwner: boolean;\n}\n\n/**\n * Configuration for the share dialog\n */\nexport interface ListShareDialogConfig {\n /**\n * The list being shared\n */\n listId: string;\n\n /**\n * List name for display\n */\n listName: string;\n\n /**\n * Current user ID\n */\n currentUserId: string;\n\n /**\n * Whether the current user is the owner\n */\n isOwner: boolean;\n}\n\n/**\n * Result from the share dialog\n */\nexport interface ListShareDialogResult {\n /**\n * Action taken\n */\n action: 'apply' | 'cancel';\n\n /**\n * Shares that were added\n */\n sharesAdded: ListShareInfo[];\n\n /**\n * Shares that were updated\n */\n sharesUpdated: ListShareInfo[];\n\n /**\n * Shares that were removed\n */\n sharesRemoved: string[];\n}\n"]}
1
+ {"version":3,"file":"list-sharing.models.js","sourceRoot":"","sources":["../../../src/lib/models/list-sharing.models.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Permission levels for list sharing\n * Maps to the PermissionLevel values in ResourcePermission table\n */\nexport type ListPermissionLevel = 'View' | 'Edit' | 'Owner';\n\n/**\n * Information about a list share\n */\nexport interface ListShareInfo {\n /**\n * The ResourcePermission record ID\n */\n shareId: string;\n\n /**\n * The List ID being shared\n */\n listId: string;\n\n /**\n * Type of recipient\n */\n type: 'User' | 'Role';\n\n /**\n * User or Role ID\n */\n recipientId: string;\n\n /**\n * Name of the user or role\n */\n recipientName: string;\n\n /**\n * Email (only for users)\n */\n recipientEmail?: string;\n\n /**\n * Permission level granted\n */\n permissionLevel: ListPermissionLevel;\n\n /**\n * Status of the share (Approved, Pending, Rejected)\n */\n status: 'Approved' | 'Pending' | 'Rejected';\n\n /**\n * When sharing starts (optional)\n */\n startSharingAt?: Date;\n\n /**\n * When sharing ends (optional)\n */\n endSharingAt?: Date;\n}\n\n/**\n * Result of a share operation\n */\nexport interface ListShareResult {\n /**\n * Whether the operation succeeded\n */\n success: boolean;\n\n /**\n * The share ID (ResourcePermission ID) if successful\n */\n shareId?: string;\n\n /**\n * Message describing the result\n */\n message: string;\n}\n\n/**\n * Recipient for sharing (used in autocomplete)\n */\nexport interface ShareRecipient {\n /**\n * User or Role ID\n */\n id: string;\n\n /**\n * Display name\n */\n name: string;\n\n /**\n * Email (only for users)\n */\n email?: string;\n\n /**\n * Type of recipient\n */\n type: 'User' | 'Role';\n}\n\n/**\n * Summary of sharing status for a list\n */\nexport interface ListSharingSummary {\n /**\n * List ID\n */\n listId: string;\n\n /**\n * Total number of shares\n */\n totalShares: number;\n\n /**\n * Number of user shares\n */\n userShares: number;\n\n /**\n * Number of role shares\n */\n roleShares: number;\n\n /**\n * Is this list shared with the current user (not owner)\n */\n isSharedWithMe: boolean;\n\n /**\n * Has the current user shared this list with others\n */\n isSharedByMe: boolean;\n}\n\n/**\n * Permission capabilities based on role\n */\nexport interface ListPermissions {\n /**\n * Can view the list contents\n */\n canView: boolean;\n\n /**\n * Can add/remove items from the list\n */\n canEditItems: boolean;\n\n /**\n * Can edit list properties (name, description, category)\n */\n canEditProperties: boolean;\n\n /**\n * Can share the list with others\n */\n canShare: boolean;\n\n /**\n * Can delete the list\n */\n canDelete: boolean;\n\n /**\n * The effective permission level\n */\n effectiveLevel: ListPermissionLevel | 'None';\n\n /**\n * Whether the user is the owner\n */\n isOwner: boolean;\n}\n\n/**\n * Configuration for the share dialog\n */\nexport interface ListShareDialogConfig {\n /**\n * The list being shared\n */\n listId: string;\n\n /**\n * List name for display\n */\n listName: string;\n\n /**\n * Current user ID\n */\n currentUserId: string;\n\n /**\n * Whether the current user is the owner\n */\n isOwner: boolean;\n}\n\n/**\n * Result from the share dialog\n */\nexport interface ListShareDialogResult {\n /**\n * Action taken\n */\n action: 'apply' | 'cancel';\n\n /**\n * Shares that were added\n */\n sharesAdded: ListShareInfo[];\n\n /**\n * Shares that were updated\n */\n sharesUpdated: ListShareInfo[];\n\n /**\n * Shares that were removed\n */\n sharesRemoved: string[];\n\n /**\n * Whether the user wants newly-added shares to fan out as notifications\n * to the recipient (email + in-app, per their preferences). Default true.\n */\n notifyByEmail?: boolean;\n}\n"]}
@@ -1,17 +1,26 @@
1
1
  import * as i0 from "@angular/core";
2
- import * as i1 from "./components/list-management-dialog/list-management-dialog.component";
3
- import * as i2 from "./components/list-share-dialog/list-share-dialog.component";
4
- import * as i3 from "@angular/common";
5
- import * as i4 from "@angular/forms";
6
- import * as i5 from "@memberjunction/ng-ui-components";
7
- import * as i6 from "@memberjunction/ng-container-directives";
8
- import * as i7 from "@memberjunction/ng-shared-generic";
2
+ import * as i1 from "./components/audience-source-picker/audience-source-picker.component";
3
+ import * as i2 from "./components/audience-source-summary/audience-source-summary.component";
4
+ import * as i3 from "./components/list-audit-log/list-audit-log.component";
5
+ import * as i4 from "./components/list-delta-confirm/list-delta-confirm.component";
6
+ import * as i5 from "./components/list-invitations/list-invitations.component";
7
+ import * as i6 from "./components/list-management-dialog/list-management-dialog.component";
8
+ import * as i7 from "./components/list-share-dialog/list-share-dialog.component";
9
+ import * as i8 from "./components/list-stats/list-stats.component";
10
+ import * as i9 from "./components/shared-with-me/shared-with-me.component";
11
+ import * as i10 from "./components/save-view-as-list-dialog/save-view-as-list-dialog.component";
12
+ import * as i11 from "./components/tag-chips/tag-chips.component";
13
+ import * as i12 from "@angular/common";
14
+ import * as i13 from "@angular/forms";
15
+ import * as i14 from "@memberjunction/ng-ui-components";
16
+ import * as i15 from "@memberjunction/ng-container-directives";
17
+ import * as i16 from "@memberjunction/ng-shared-generic";
9
18
  /**
10
19
  * Module providing list management components for MemberJunction.
11
20
  */
12
21
  export declare class ListManagementModule {
13
22
  static ɵfac: i0.ɵɵFactoryDeclaration<ListManagementModule, never>;
14
- static ɵmod: i0.ɵɵNgModuleDeclaration<ListManagementModule, [typeof i1.ListManagementDialogComponent, typeof i2.ListShareDialogComponent], [typeof i3.CommonModule, typeof i4.FormsModule, typeof i5.MJButtonDirective, typeof i5.MJDialogComponent, typeof i5.MJDialogActionsComponent, typeof i5.MJDropdownComponent, typeof i6.ContainerDirectivesModule, typeof i7.SharedGenericModule], [typeof i1.ListManagementDialogComponent, typeof i2.ListShareDialogComponent]>;
23
+ static ɵmod: i0.ɵɵNgModuleDeclaration<ListManagementModule, [typeof i1.AudienceSourcePickerComponent, typeof i2.AudienceSourceSummaryComponent, typeof i3.ListAuditLogComponent, typeof i4.ListDeltaConfirmComponent, typeof i5.ListInvitationsComponent, typeof i6.ListManagementDialogComponent, typeof i7.ListShareDialogComponent, typeof i8.ListStatsComponent, typeof i9.ListsSharedWithMeComponent, typeof i10.SaveViewAsListDialogComponent, typeof i11.TagChipsComponent], [typeof i12.CommonModule, typeof i13.FormsModule, typeof i14.MJButtonDirective, typeof i14.MJDialogComponent, typeof i14.MJDialogActionsComponent, typeof i14.MJDropdownComponent, typeof i15.ContainerDirectivesModule, typeof i16.SharedGenericModule], [typeof i1.AudienceSourcePickerComponent, typeof i2.AudienceSourceSummaryComponent, typeof i3.ListAuditLogComponent, typeof i4.ListDeltaConfirmComponent, typeof i5.ListInvitationsComponent, typeof i6.ListManagementDialogComponent, typeof i7.ListShareDialogComponent, typeof i8.ListStatsComponent, typeof i9.ListsSharedWithMeComponent, typeof i10.SaveViewAsListDialogComponent, typeof i11.TagChipsComponent]>;
15
24
  static ɵinj: i0.ɵɵInjectorDeclaration<ListManagementModule>;
16
25
  }
17
26
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":";;;;;;;;AAwBA;;GAEG;AACH,qBAwBa,oBAAoB;yCAApB,oBAAoB;0CAApB,oBAAoB;0CAApB,oBAAoB;CAAI"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAiCA;;GAEG;AACH,qBA0Ca,oBAAoB;yCAApB,oBAAoB;0CAApB,oBAAoB;0CAApB,oBAAoB;CAAI"}