@memberjunction/ng-entity-viewer 5.27.1 → 5.29.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 (30) hide show
  1. package/README.md +101 -0
  2. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +14 -1
  3. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
  4. package/dist/lib/entity-data-grid/entity-data-grid.component.js +199 -172
  5. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
  6. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +9 -1
  7. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  8. package/dist/lib/entity-viewer/entity-viewer.component.js +58 -38
  9. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  10. package/dist/lib/recycle-bin/events/recycle-bin-events.d.ts +91 -0
  11. package/dist/lib/recycle-bin/events/recycle-bin-events.d.ts.map +1 -0
  12. package/dist/lib/recycle-bin/events/recycle-bin-events.js +10 -0
  13. package/dist/lib/recycle-bin/events/recycle-bin-events.js.map +1 -0
  14. package/dist/lib/recycle-bin/recycle-bin-chip.component.d.ts +75 -0
  15. package/dist/lib/recycle-bin/recycle-bin-chip.component.d.ts.map +1 -0
  16. package/dist/lib/recycle-bin/recycle-bin-chip.component.js +228 -0
  17. package/dist/lib/recycle-bin/recycle-bin-chip.component.js.map +1 -0
  18. package/dist/lib/recycle-bin/recycle-bin.component.d.ts +178 -0
  19. package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -0
  20. package/dist/lib/recycle-bin/recycle-bin.component.js +681 -0
  21. package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -0
  22. package/dist/module.d.ts +13 -9
  23. package/dist/module.d.ts.map +1 -1
  24. package/dist/module.js +23 -7
  25. package/dist/module.js.map +1 -1
  26. package/dist/public-api.d.ts +3 -0
  27. package/dist/public-api.d.ts.map +1 -1
  28. package/dist/public-api.js +4 -0
  29. package/dist/public-api.js.map +1 -1
  30. package/package.json +13 -11
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Event arg types for {@link RecycleBinComponent}.
3
+ *
4
+ * Follows the same Before/After cancelable pattern used by
5
+ * {@link EntityDataGridComponent}: any event whose name starts with
6
+ * `Before` carries `cancel: boolean` and `cancelReason?: string` so
7
+ * consumers can intercept and abort or take over processing.
8
+ */
9
+ import { MJRecordChangeEntity } from '@memberjunction/core-entities';
10
+ /** Forward type to avoid circular dependency with the component. */
11
+ export type RecycleBinComponentRef = unknown;
12
+ /**
13
+ * Base shape for every Recycle Bin event.
14
+ */
15
+ export interface RecycleBinEventArgs {
16
+ /** The Recycle Bin component instance that raised the event. */
17
+ bin: RecycleBinComponentRef;
18
+ /** When the event was raised. */
19
+ timestamp: Date;
20
+ /** The entity the bin is operating on. */
21
+ entityName: string;
22
+ }
23
+ /**
24
+ * Base shape for cancelable Recycle Bin events. Set `cancel = true` to
25
+ * abort the operation; the matching `after*` event will not fire.
26
+ */
27
+ export interface CancelableRecycleBinEventArgs extends RecycleBinEventArgs {
28
+ /** Set to true to abort. */
29
+ cancel: boolean;
30
+ /** Optional reason surfaced by the consumer. */
31
+ cancelReason?: string;
32
+ }
33
+ export interface BeforeRecycleBinOpenEventArgs extends CancelableRecycleBinEventArgs {
34
+ /** Reserved for future filter/scope params the consumer may want to validate. */
35
+ readonly _kind: 'beforeRecycleBinOpen';
36
+ }
37
+ export interface AfterRecycleBinOpenEventArgs extends RecycleBinEventArgs {
38
+ /** Number of distinct deleted records discovered when the bin opened. */
39
+ deletedRecordCount: number;
40
+ readonly _kind: 'afterRecycleBinOpen';
41
+ }
42
+ /**
43
+ * One deleted-record entry surfaced by the bin. Built from the most recent
44
+ * Delete RecordChange for a given EntityID + RecordID.
45
+ */
46
+ export interface RecycleBinEntry {
47
+ /** The Delete RecordChange row that captured this snapshot. */
48
+ RecordChange: MJRecordChangeEntity;
49
+ /** The composite-key string that identified the record. */
50
+ RecordID: string;
51
+ /** The user-friendly summary line (e.g., a name field from the snapshot). */
52
+ DisplaySummary: string;
53
+ /** Up to N supporting fields chosen heuristically for the card. */
54
+ SupportingFields: Array<{
55
+ Name: string;
56
+ DisplayName: string;
57
+ Value: string;
58
+ }>;
59
+ }
60
+ export interface BeforeRecordRestoreEventArgs extends CancelableRecycleBinEventArgs {
61
+ entry: RecycleBinEntry;
62
+ readonly _kind: 'beforeRecordRestore';
63
+ }
64
+ export interface AfterRecordRestoreEventArgs extends RecycleBinEventArgs {
65
+ entry: RecycleBinEntry;
66
+ /** True when the underlying insert succeeded. */
67
+ success: boolean;
68
+ /** Error message when success is false. */
69
+ errorMessage?: string;
70
+ readonly _kind: 'afterRecordRestore';
71
+ }
72
+ export interface BeforeRestoreCommitEventArgs extends CancelableRecycleBinEventArgs {
73
+ entry: RecycleBinEntry;
74
+ /** Field values that will be applied to the new record. */
75
+ fieldValues: Array<{
76
+ FieldName: string;
77
+ Value: unknown;
78
+ }>;
79
+ /** Optional reason captured at restore time. */
80
+ reason: string | null;
81
+ readonly _kind: 'beforeRestoreCommit';
82
+ }
83
+ export interface AfterRestoreCommitEventArgs extends RecycleBinEventArgs {
84
+ entry: RecycleBinEntry;
85
+ success: boolean;
86
+ /** ID of the newly created record (when success). */
87
+ newRecordID?: string;
88
+ errorMessage?: string;
89
+ readonly _kind: 'afterRestoreCommit';
90
+ }
91
+ //# sourceMappingURL=recycle-bin-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle-bin-events.d.ts","sourceRoot":"","sources":["../../../../src/lib/recycle-bin/events/recycle-bin-events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,GAAG,EAAE,sBAAsB,CAAC;IAC5B,iCAAiC;IACjC,SAAS,EAAE,IAAI,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,6BAA8B,SAAQ,mBAAmB;IACxE,4BAA4B;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,WAAW,6BAA8B,SAAQ,6BAA6B;IAClF,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;CACxC;AAED,MAAM,WAAW,4BAA6B,SAAQ,mBAAmB;IACvE,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;CACvC;AAID;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,YAAY,EAAE,oBAAoB,CAAC;IACnC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,gBAAgB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,4BAA6B,SAAQ,6BAA6B;IACjF,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;CACvC;AAED,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,KAAK,EAAE,eAAe,CAAC;IACvB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC;AAID,MAAM,WAAW,4BAA6B,SAAQ,6BAA6B;IACjF,KAAK,EAAE,eAAe,CAAC;IACvB,2DAA2D;IAC3D,WAAW,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1D,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;CACvC;AAED,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Event arg types for {@link RecycleBinComponent}.
3
+ *
4
+ * Follows the same Before/After cancelable pattern used by
5
+ * {@link EntityDataGridComponent}: any event whose name starts with
6
+ * `Before` carries `cancel: boolean` and `cancelReason?: string` so
7
+ * consumers can intercept and abort or take over processing.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=recycle-bin-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle-bin-events.js","sourceRoot":"","sources":["../../../../src/lib/recycle-bin/events/recycle-bin-events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * Event arg types for {@link RecycleBinComponent}.\n *\n * Follows the same Before/After cancelable pattern used by\n * {@link EntityDataGridComponent}: any event whose name starts with\n * `Before` carries `cancel: boolean` and `cancelReason?: string` so\n * consumers can intercept and abort or take over processing.\n */\n\nimport { MJRecordChangeEntity } from '@memberjunction/core-entities';\n\n/** Forward type to avoid circular dependency with the component. */\nexport type RecycleBinComponentRef = unknown;\n\n/**\n * Base shape for every Recycle Bin event.\n */\nexport interface RecycleBinEventArgs {\n /** The Recycle Bin component instance that raised the event. */\n bin: RecycleBinComponentRef;\n /** When the event was raised. */\n timestamp: Date;\n /** The entity the bin is operating on. */\n entityName: string;\n}\n\n/**\n * Base shape for cancelable Recycle Bin events. Set `cancel = true` to\n * abort the operation; the matching `after*` event will not fire.\n */\nexport interface CancelableRecycleBinEventArgs extends RecycleBinEventArgs {\n /** Set to true to abort. */\n cancel: boolean;\n /** Optional reason surfaced by the consumer. */\n cancelReason?: string;\n}\n\n// ─── Open / Close ──────────────────────────────────────────────────\n\nexport interface BeforeRecycleBinOpenEventArgs extends CancelableRecycleBinEventArgs {\n /** Reserved for future filter/scope params the consumer may want to validate. */\n readonly _kind: 'beforeRecycleBinOpen';\n}\n\nexport interface AfterRecycleBinOpenEventArgs extends RecycleBinEventArgs {\n /** Number of distinct deleted records discovered when the bin opened. */\n deletedRecordCount: number;\n readonly _kind: 'afterRecycleBinOpen';\n}\n\n// ─── Per-record restore (the user clicked Restore on a single card) ─\n\n/**\n * One deleted-record entry surfaced by the bin. Built from the most recent\n * Delete RecordChange for a given EntityID + RecordID.\n */\nexport interface RecycleBinEntry {\n /** The Delete RecordChange row that captured this snapshot. */\n RecordChange: MJRecordChangeEntity;\n /** The composite-key string that identified the record. */\n RecordID: string;\n /** The user-friendly summary line (e.g., a name field from the snapshot). */\n DisplaySummary: string;\n /** Up to N supporting fields chosen heuristically for the card. */\n SupportingFields: Array<{ Name: string; DisplayName: string; Value: string }>;\n}\n\nexport interface BeforeRecordRestoreEventArgs extends CancelableRecycleBinEventArgs {\n entry: RecycleBinEntry;\n readonly _kind: 'beforeRecordRestore';\n}\n\nexport interface AfterRecordRestoreEventArgs extends RecycleBinEventArgs {\n entry: RecycleBinEntry;\n /** True when the underlying insert succeeded. */\n success: boolean;\n /** Error message when success is false. */\n errorMessage?: string;\n readonly _kind: 'afterRecordRestore';\n}\n\n// ─── Restore commit (after the user confirms in the preview panel) ──\n\nexport interface BeforeRestoreCommitEventArgs extends CancelableRecycleBinEventArgs {\n entry: RecycleBinEntry;\n /** Field values that will be applied to the new record. */\n fieldValues: Array<{ FieldName: string; Value: unknown }>;\n /** Optional reason captured at restore time. */\n reason: string | null;\n readonly _kind: 'beforeRestoreCommit';\n}\n\nexport interface AfterRestoreCommitEventArgs extends RecycleBinEventArgs {\n entry: RecycleBinEntry;\n success: boolean;\n /** ID of the newly created record (when success). */\n newRecordID?: string;\n errorMessage?: string;\n readonly _kind: 'afterRestoreCommit';\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import { ChangeDetectorRef, EventEmitter, OnInit } from '@angular/core';
2
+ import { UserInfo } from '@memberjunction/core';
3
+ import { AfterRecordRestoreEventArgs, AfterRecycleBinOpenEventArgs, AfterRestoreCommitEventArgs, BeforeRecordRestoreEventArgs, BeforeRecycleBinOpenEventArgs, BeforeRestoreCommitEventArgs } from './events/recycle-bin-events';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Tiny composite that renders a Recycle Bin **chip button** (with live
7
+ * deleted-record count badge) and hosts the {@link RecycleBinComponent}
8
+ * slide-in panel that opens when clicked.
9
+ *
10
+ * Designed to be dropped into any toolbar in three lines:
11
+ *
12
+ * @example
13
+ * <mj-recycle-bin-chip
14
+ * *ngIf="ShowRecycleBin"
15
+ * [EntityName]="effectiveEntity?.Name">
16
+ * </mj-recycle-bin-chip>
17
+ *
18
+ * The chip auto-hides itself when:
19
+ * - No `EntityName` is provided
20
+ * - The entity has `TrackRecordChanges = false`
21
+ * - The user lacks `CanDelete` permission on the entity
22
+ * - The deleted-record count is zero
23
+ *
24
+ * Each of those is the right behavior to avoid cluttering toolbars on
25
+ * entities where the bin is irrelevant.
26
+ *
27
+ * Consumers who want to intercept any bin action can pass through the
28
+ * standard cancelable Before/After events.
29
+ */
30
+ export declare class RecycleBinChipComponent implements OnInit {
31
+ private cdr;
32
+ /**
33
+ * Entity name whose deleted records will be surfaced. When null/empty,
34
+ * the chip hides itself entirely.
35
+ */
36
+ private _entityName;
37
+ set EntityName(value: string | null);
38
+ get EntityName(): string | null;
39
+ /**
40
+ * Optional context user for permission checks. Falls back to
41
+ * `Metadata.Provider.CurrentUser` when omitted.
42
+ */
43
+ ContextUser: UserInfo | null;
44
+ BeforeRecycleBinOpen: EventEmitter<BeforeRecycleBinOpenEventArgs>;
45
+ AfterRecycleBinOpen: EventEmitter<AfterRecycleBinOpenEventArgs>;
46
+ BeforeRecordRestore: EventEmitter<BeforeRecordRestoreEventArgs>;
47
+ AfterRecordRestore: EventEmitter<AfterRecordRestoreEventArgs>;
48
+ BeforeRestoreCommit: EventEmitter<BeforeRestoreCommitEventArgs>;
49
+ AfterRestoreCommit: EventEmitter<AfterRestoreCommitEventArgs>;
50
+ DeletedCount: number;
51
+ PanelVisible: boolean;
52
+ IsVisible: boolean;
53
+ CanDelete: boolean;
54
+ TracksChanges: boolean;
55
+ private isInitialized;
56
+ constructor(cdr: ChangeDetectorRef);
57
+ ngOnInit(): void;
58
+ Toggle(): void;
59
+ OnPanelClosed(): void;
60
+ /**
61
+ * After-events bubble through but we also use them to refresh the count
62
+ * — restoring a record means it's no longer "deleted".
63
+ */
64
+ OnAfterRecycleBinOpen(e: AfterRecycleBinOpenEventArgs): void;
65
+ OnAfterRestoreCommit(e: AfterRestoreCommitEventArgs): void;
66
+ /**
67
+ * Loads the deleted-record count and visibility flags. Called on init,
68
+ * when EntityName changes, and after the panel closes (so a restore
69
+ * decrements the badge).
70
+ */
71
+ private refreshCount;
72
+ static ɵfac: i0.ɵɵFactoryDeclaration<RecycleBinChipComponent, never>;
73
+ static ɵcmp: i0.ɵɵComponentDeclaration<RecycleBinChipComponent, "mj-recycle-bin-chip", never, { "EntityName": { "alias": "EntityName"; "required": false; }; "ContextUser": { "alias": "ContextUser"; "required": false; }; }, { "BeforeRecycleBinOpen": "BeforeRecycleBinOpen"; "AfterRecycleBinOpen": "AfterRecycleBinOpen"; "BeforeRecordRestore": "BeforeRecordRestore"; "AfterRecordRestore": "AfterRecordRestore"; "BeforeRestoreCommit": "BeforeRestoreCommit"; "AfterRestoreCommit": "AfterRestoreCommit"; }, never, never, false, never>;
74
+ }
75
+ //# sourceMappingURL=recycle-bin-chip.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle-bin-chip.component.d.ts","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin-chip.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,YAAY,EAEZ,MAAM,EAGP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAqB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC7B,MAAM,6BAA6B,CAAC;;AAErC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBA+Da,uBAAwB,YAAW,MAAM;IA0CxC,OAAO,CAAC,GAAG;IAzCvB;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAuB;IAC1C,IACI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAKlC;IACD,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED;;;OAGG;IACM,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAInC,oBAAoB,8CAAqD;IACzE,mBAAmB,6CAAoD;IACvE,mBAAmB,6CAAoD;IACvE,kBAAkB,4CAAmD;IACrE,mBAAmB,6CAAoD;IACvE,kBAAkB,4CAAmD;IAIxE,YAAY,SAAK;IACjB,YAAY,UAAS;IACrB,SAAS,UAAS;IAClB,SAAS,UAAS;IAClB,aAAa,UAAS;IAE7B,OAAO,CAAC,aAAa,CAAS;gBAEV,GAAG,EAAE,iBAAiB;IAE1C,QAAQ,IAAI,IAAI;IAKT,MAAM,IAAI,IAAI;IAKd,aAAa,IAAI,IAAI;IAM5B;;;OAGG;IACI,qBAAqB,CAAC,CAAC,EAAE,4BAA4B,GAAG,IAAI;IAM5D,oBAAoB,CAAC,CAAC,EAAE,2BAA2B,GAAG,IAAI;IAMjE;;;;OAIG;YACW,YAAY;yCAjFf,uBAAuB;2CAAvB,uBAAuB;CA6InC"}
@@ -0,0 +1,228 @@
1
+ import { Component, ChangeDetectionStrategy, EventEmitter, Input, Output, ViewEncapsulation, } from '@angular/core';
2
+ import { Metadata, RunView } from '@memberjunction/core';
3
+ import * as i0 from "@angular/core";
4
+ import * as i1 from "./recycle-bin.component";
5
+ function RecycleBinChipComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
6
+ const _r1 = i0.ɵɵgetCurrentView();
7
+ i0.ɵɵelementStart(0, "button", 0);
8
+ i0.ɵɵlistener("click", function RecycleBinChipComponent_Conditional_0_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Toggle()); });
9
+ i0.ɵɵelement(1, "i", 1);
10
+ i0.ɵɵelementStart(2, "span", 2);
11
+ i0.ɵɵtext(3, "Recycle Bin");
12
+ i0.ɵɵelementEnd();
13
+ i0.ɵɵelementStart(4, "span", 3);
14
+ i0.ɵɵtext(5);
15
+ i0.ɵɵelementEnd()();
16
+ i0.ɵɵelementStart(6, "mj-recycle-bin", 4);
17
+ i0.ɵɵlistener("Closed", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_Closed_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPanelClosed()); })("BeforeRecycleBinOpen", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_BeforeRecycleBinOpen_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.BeforeRecycleBinOpen.emit($event)); })("AfterRecycleBinOpen", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_AfterRecycleBinOpen_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnAfterRecycleBinOpen($event)); })("BeforeRecordRestore", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_BeforeRecordRestore_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.BeforeRecordRestore.emit($event)); })("AfterRecordRestore", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_AfterRecordRestore_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.AfterRecordRestore.emit($event)); })("BeforeRestoreCommit", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_BeforeRestoreCommit_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.BeforeRestoreCommit.emit($event)); })("AfterRestoreCommit", function RecycleBinChipComponent_Conditional_0_Template_mj_recycle_bin_AfterRestoreCommit_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnAfterRestoreCommit($event)); });
18
+ i0.ɵɵelementEnd();
19
+ } if (rf & 2) {
20
+ const ctx_r1 = i0.ɵɵnextContext();
21
+ i0.ɵɵattribute("title", "Recycle Bin \u00B7 " + ctx_r1.DeletedCount + " deleted record" + (ctx_r1.DeletedCount === 1 ? "" : "s"));
22
+ i0.ɵɵadvance(5);
23
+ i0.ɵɵtextInterpolate(ctx_r1.DeletedCount);
24
+ i0.ɵɵadvance();
25
+ i0.ɵɵproperty("Visible", ctx_r1.PanelVisible)("EntityName", ctx_r1.EntityName)("ContextUser", ctx_r1.ContextUser);
26
+ } }
27
+ /**
28
+ * Tiny composite that renders a Recycle Bin **chip button** (with live
29
+ * deleted-record count badge) and hosts the {@link RecycleBinComponent}
30
+ * slide-in panel that opens when clicked.
31
+ *
32
+ * Designed to be dropped into any toolbar in three lines:
33
+ *
34
+ * @example
35
+ * <mj-recycle-bin-chip
36
+ * *ngIf="ShowRecycleBin"
37
+ * [EntityName]="effectiveEntity?.Name">
38
+ * </mj-recycle-bin-chip>
39
+ *
40
+ * The chip auto-hides itself when:
41
+ * - No `EntityName` is provided
42
+ * - The entity has `TrackRecordChanges = false`
43
+ * - The user lacks `CanDelete` permission on the entity
44
+ * - The deleted-record count is zero
45
+ *
46
+ * Each of those is the right behavior to avoid cluttering toolbars on
47
+ * entities where the bin is irrelevant.
48
+ *
49
+ * Consumers who want to intercept any bin action can pass through the
50
+ * standard cancelable Before/After events.
51
+ */
52
+ export class RecycleBinChipComponent {
53
+ cdr;
54
+ /**
55
+ * Entity name whose deleted records will be surfaced. When null/empty,
56
+ * the chip hides itself entirely.
57
+ */
58
+ _entityName = null;
59
+ set EntityName(value) {
60
+ if (value !== this._entityName) {
61
+ this._entityName = value;
62
+ if (this.isInitialized)
63
+ this.refreshCount();
64
+ }
65
+ }
66
+ get EntityName() {
67
+ return this._entityName;
68
+ }
69
+ /**
70
+ * Optional context user for permission checks. Falls back to
71
+ * `Metadata.Provider.CurrentUser` when omitted.
72
+ */
73
+ ContextUser = null;
74
+ // ─── Re-emitted events from the inner panel ────────────────────
75
+ BeforeRecycleBinOpen = new EventEmitter();
76
+ AfterRecycleBinOpen = new EventEmitter();
77
+ BeforeRecordRestore = new EventEmitter();
78
+ AfterRecordRestore = new EventEmitter();
79
+ BeforeRestoreCommit = new EventEmitter();
80
+ AfterRestoreCommit = new EventEmitter();
81
+ // ─── Public template state ──────────────────────────────────────
82
+ DeletedCount = 0;
83
+ PanelVisible = false;
84
+ IsVisible = false;
85
+ CanDelete = false;
86
+ TracksChanges = false;
87
+ isInitialized = false;
88
+ constructor(cdr) {
89
+ this.cdr = cdr;
90
+ }
91
+ ngOnInit() {
92
+ this.isInitialized = true;
93
+ this.refreshCount();
94
+ }
95
+ Toggle() {
96
+ this.PanelVisible = !this.PanelVisible;
97
+ this.cdr.markForCheck();
98
+ }
99
+ OnPanelClosed() {
100
+ this.PanelVisible = false;
101
+ // Refresh count — a restore may have removed an entry from the bin
102
+ this.refreshCount();
103
+ }
104
+ /**
105
+ * After-events bubble through but we also use them to refresh the count
106
+ * — restoring a record means it's no longer "deleted".
107
+ */
108
+ OnAfterRecycleBinOpen(e) {
109
+ this.DeletedCount = e.deletedRecordCount;
110
+ this.AfterRecycleBinOpen.emit(e);
111
+ this.cdr.markForCheck();
112
+ }
113
+ OnAfterRestoreCommit(e) {
114
+ if (e.success)
115
+ this.DeletedCount = Math.max(0, this.DeletedCount - 1);
116
+ this.AfterRestoreCommit.emit(e);
117
+ this.cdr.markForCheck();
118
+ }
119
+ /**
120
+ * Loads the deleted-record count and visibility flags. Called on init,
121
+ * when EntityName changes, and after the panel closes (so a restore
122
+ * decrements the badge).
123
+ */
124
+ async refreshCount() {
125
+ this.IsVisible = false;
126
+ this.DeletedCount = 0;
127
+ if (!this._entityName) {
128
+ this.cdr.markForCheck();
129
+ return;
130
+ }
131
+ const md = new Metadata();
132
+ const entityInfo = md.Entities.find(e => e.Name.trim().toLowerCase() === this._entityName.trim().toLowerCase());
133
+ if (!entityInfo) {
134
+ this.cdr.markForCheck();
135
+ return;
136
+ }
137
+ this.TracksChanges = !!entityInfo.TrackRecordChanges;
138
+ if (!this.TracksChanges) {
139
+ this.cdr.markForCheck();
140
+ return;
141
+ }
142
+ const effectiveUser = this.ContextUser ?? md.CurrentUser;
143
+ if (!effectiveUser) {
144
+ this.cdr.markForCheck();
145
+ return;
146
+ }
147
+ const perms = entityInfo.GetUserPermisions(effectiveUser);
148
+ this.CanDelete = perms?.CanDelete ?? false;
149
+ if (!this.CanDelete) {
150
+ this.cdr.markForCheck();
151
+ return;
152
+ }
153
+ // Cheap count query — just IDs, capped low
154
+ try {
155
+ const rv = new RunView();
156
+ const result = await rv.RunView({
157
+ EntityName: 'MJ: Record Changes',
158
+ Fields: ['ID', 'RecordID'],
159
+ ExtraFilter: `EntityID='${entityInfo.ID}' AND Type='Delete'`,
160
+ OrderBy: 'ChangedAt DESC',
161
+ MaxRows: 500,
162
+ ResultType: 'simple',
163
+ }, this.ContextUser ?? undefined);
164
+ if (result.Success) {
165
+ // Distinct RecordIDs (a record might have been deleted, recreated, deleted again)
166
+ const distinct = new Set(result.Results.map(r => r.RecordID));
167
+ this.DeletedCount = distinct.size;
168
+ }
169
+ }
170
+ catch {
171
+ // Silently ignore — chip just won't show
172
+ }
173
+ this.IsVisible = this.DeletedCount > 0;
174
+ this.cdr.markForCheck();
175
+ }
176
+ static ɵfac = function RecycleBinChipComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RecycleBinChipComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
177
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecycleBinChipComponent, selectors: [["mj-recycle-bin-chip"]], inputs: { EntityName: "EntityName", ContextUser: "ContextUser" }, outputs: { BeforeRecycleBinOpen: "BeforeRecycleBinOpen", AfterRecycleBinOpen: "AfterRecycleBinOpen", BeforeRecordRestore: "BeforeRecordRestore", AfterRecordRestore: "AfterRecordRestore", BeforeRestoreCommit: "BeforeRestoreCommit", AfterRestoreCommit: "AfterRestoreCommit" }, standalone: false, decls: 1, vars: 1, consts: [["type", "button", 1, "rbc-chip", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-trash-can-arrow-up"], [1, "rbc-chip-label"], [1, "rbc-chip-count"], [3, "Closed", "BeforeRecycleBinOpen", "AfterRecycleBinOpen", "BeforeRecordRestore", "AfterRecordRestore", "BeforeRestoreCommit", "AfterRestoreCommit", "Visible", "EntityName", "ContextUser"]], template: function RecycleBinChipComponent_Template(rf, ctx) { if (rf & 1) {
178
+ i0.ɵɵconditionalCreate(0, RecycleBinChipComponent_Conditional_0_Template, 7, 5);
179
+ } if (rf & 2) {
180
+ i0.ɵɵconditional(ctx.IsVisible ? 0 : -1);
181
+ } }, dependencies: [i1.RecycleBinComponent], styles: ["\n .rbc-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 11px;\n border-radius: 16px;\n font-size: 12px;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-info) 80%, var(--mj-text-secondary));\n border: 1px solid color-mix(in srgb, var(--mj-status-info) 25%, var(--mj-border-default));\n cursor: pointer;\n font-family: inherit;\n transition: background-color 0.1s, border-color 0.1s;\n }\n .rbc-chip:hover {\n background: color-mix(in srgb, var(--mj-status-info) 14%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n }\n .rbc-chip-label {\n /* always show on wider screens; hide on narrow if needed */\n }\n .rbc-chip-count {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse, #fff);\n border-radius: 10px;\n padding: 1px 7px;\n font-size: 10px;\n font-weight: 700;\n }\n "], encapsulation: 2, changeDetection: 0 });
182
+ }
183
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecycleBinChipComponent, [{
184
+ type: Component,
185
+ args: [{ standalone: false, selector: 'mj-recycle-bin-chip', template: `
186
+ @if (IsVisible) {
187
+ <button class="rbc-chip"
188
+ type="button"
189
+ (click)="Toggle()"
190
+ [attr.title]="'Recycle Bin · ' + DeletedCount + ' deleted record' + (DeletedCount === 1 ? '' : 's')">
191
+ <i class="fa-solid fa-trash-can-arrow-up" aria-hidden="true"></i>
192
+ <span class="rbc-chip-label">Recycle Bin</span>
193
+ <span class="rbc-chip-count">{{ DeletedCount }}</span>
194
+ </button>
195
+
196
+ <mj-recycle-bin
197
+ [Visible]="PanelVisible"
198
+ [EntityName]="EntityName"
199
+ [ContextUser]="ContextUser"
200
+ (Closed)="OnPanelClosed()"
201
+ (BeforeRecycleBinOpen)="BeforeRecycleBinOpen.emit($event)"
202
+ (AfterRecycleBinOpen)="OnAfterRecycleBinOpen($event)"
203
+ (BeforeRecordRestore)="BeforeRecordRestore.emit($event)"
204
+ (AfterRecordRestore)="AfterRecordRestore.emit($event)"
205
+ (BeforeRestoreCommit)="BeforeRestoreCommit.emit($event)"
206
+ (AfterRestoreCommit)="OnAfterRestoreCommit($event)">
207
+ </mj-recycle-bin>
208
+ }
209
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["\n .rbc-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 11px;\n border-radius: 16px;\n font-size: 12px;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-info) 80%, var(--mj-text-secondary));\n border: 1px solid color-mix(in srgb, var(--mj-status-info) 25%, var(--mj-border-default));\n cursor: pointer;\n font-family: inherit;\n transition: background-color 0.1s, border-color 0.1s;\n }\n .rbc-chip:hover {\n background: color-mix(in srgb, var(--mj-status-info) 14%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n }\n .rbc-chip-label {\n /* always show on wider screens; hide on narrow if needed */\n }\n .rbc-chip-count {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse, #fff);\n border-radius: 10px;\n padding: 1px 7px;\n font-size: 10px;\n font-weight: 700;\n }\n "] }]
210
+ }], () => [{ type: i0.ChangeDetectorRef }], { EntityName: [{
211
+ type: Input
212
+ }], ContextUser: [{
213
+ type: Input
214
+ }], BeforeRecycleBinOpen: [{
215
+ type: Output
216
+ }], AfterRecycleBinOpen: [{
217
+ type: Output
218
+ }], BeforeRecordRestore: [{
219
+ type: Output
220
+ }], AfterRecordRestore: [{
221
+ type: Output
222
+ }], BeforeRestoreCommit: [{
223
+ type: Output
224
+ }], AfterRestoreCommit: [{
225
+ type: Output
226
+ }] }); })();
227
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecycleBinChipComponent, { className: "RecycleBinChipComponent", filePath: "src/lib/recycle-bin/recycle-bin-chip.component.ts", lineNumber: 109 }); })();
228
+ //# sourceMappingURL=recycle-bin-chip.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle-bin-chip.component.js","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin-chip.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n EventEmitter,\n Input,\n OnInit,\n Output,\n ViewEncapsulation,\n} from '@angular/core';\nimport { Metadata, RunView, UserInfo } from '@memberjunction/core';\nimport {\n AfterRecordRestoreEventArgs,\n AfterRecycleBinOpenEventArgs,\n AfterRestoreCommitEventArgs,\n BeforeRecordRestoreEventArgs,\n BeforeRecycleBinOpenEventArgs,\n BeforeRestoreCommitEventArgs,\n} from './events/recycle-bin-events';\n\n/**\n * Tiny composite that renders a Recycle Bin **chip button** (with live\n * deleted-record count badge) and hosts the {@link RecycleBinComponent}\n * slide-in panel that opens when clicked.\n *\n * Designed to be dropped into any toolbar in three lines:\n *\n * @example\n * <mj-recycle-bin-chip\n * *ngIf=\"ShowRecycleBin\"\n * [EntityName]=\"effectiveEntity?.Name\">\n * </mj-recycle-bin-chip>\n *\n * The chip auto-hides itself when:\n * - No `EntityName` is provided\n * - The entity has `TrackRecordChanges = false`\n * - The user lacks `CanDelete` permission on the entity\n * - The deleted-record count is zero\n *\n * Each of those is the right behavior to avoid cluttering toolbars on\n * entities where the bin is irrelevant.\n *\n * Consumers who want to intercept any bin action can pass through the\n * standard cancelable Before/After events.\n */\n@Component({\n standalone: false,\n selector: 'mj-recycle-bin-chip',\n template: `\n @if (IsVisible) {\n <button class=\"rbc-chip\"\n type=\"button\"\n (click)=\"Toggle()\"\n [attr.title]=\"'Recycle Bin · ' + DeletedCount + ' deleted record' + (DeletedCount === 1 ? '' : 's')\">\n <i class=\"fa-solid fa-trash-can-arrow-up\" aria-hidden=\"true\"></i>\n <span class=\"rbc-chip-label\">Recycle Bin</span>\n <span class=\"rbc-chip-count\">{{ DeletedCount }}</span>\n </button>\n\n <mj-recycle-bin\n [Visible]=\"PanelVisible\"\n [EntityName]=\"EntityName\"\n [ContextUser]=\"ContextUser\"\n (Closed)=\"OnPanelClosed()\"\n (BeforeRecycleBinOpen)=\"BeforeRecycleBinOpen.emit($event)\"\n (AfterRecycleBinOpen)=\"OnAfterRecycleBinOpen($event)\"\n (BeforeRecordRestore)=\"BeforeRecordRestore.emit($event)\"\n (AfterRecordRestore)=\"AfterRecordRestore.emit($event)\"\n (BeforeRestoreCommit)=\"BeforeRestoreCommit.emit($event)\"\n (AfterRestoreCommit)=\"OnAfterRestoreCommit($event)\">\n </mj-recycle-bin>\n }\n `,\n styles: [`\n .rbc-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 11px;\n border-radius: 16px;\n font-size: 12px;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-info) 80%, var(--mj-text-secondary));\n border: 1px solid color-mix(in srgb, var(--mj-status-info) 25%, var(--mj-border-default));\n cursor: pointer;\n font-family: inherit;\n transition: background-color 0.1s, border-color 0.1s;\n }\n .rbc-chip:hover {\n background: color-mix(in srgb, var(--mj-status-info) 14%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n }\n .rbc-chip-label {\n /* always show on wider screens; hide on narrow if needed */\n }\n .rbc-chip-count {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse, #fff);\n border-radius: 10px;\n padding: 1px 7px;\n font-size: 10px;\n font-weight: 700;\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class RecycleBinChipComponent implements OnInit {\n /**\n * Entity name whose deleted records will be surfaced. When null/empty,\n * the chip hides itself entirely.\n */\n private _entityName: string | null = null;\n @Input()\n set EntityName(value: string | null) {\n if (value !== this._entityName) {\n this._entityName = value;\n if (this.isInitialized) this.refreshCount();\n }\n }\n get EntityName(): string | null {\n return this._entityName;\n }\n\n /**\n * Optional context user for permission checks. Falls back to\n * `Metadata.Provider.CurrentUser` when omitted.\n */\n @Input() ContextUser: UserInfo | null = null;\n\n // ─── Re-emitted events from the inner panel ────────────────────\n\n @Output() BeforeRecycleBinOpen = new EventEmitter<BeforeRecycleBinOpenEventArgs>();\n @Output() AfterRecycleBinOpen = new EventEmitter<AfterRecycleBinOpenEventArgs>();\n @Output() BeforeRecordRestore = new EventEmitter<BeforeRecordRestoreEventArgs>();\n @Output() AfterRecordRestore = new EventEmitter<AfterRecordRestoreEventArgs>();\n @Output() BeforeRestoreCommit = new EventEmitter<BeforeRestoreCommitEventArgs>();\n @Output() AfterRestoreCommit = new EventEmitter<AfterRestoreCommitEventArgs>();\n\n // ─── Public template state ──────────────────────────────────────\n\n public DeletedCount = 0;\n public PanelVisible = false;\n public IsVisible = false;\n public CanDelete = false;\n public TracksChanges = false;\n\n private isInitialized = false;\n\n constructor(private cdr: ChangeDetectorRef) {}\n\n ngOnInit(): void {\n this.isInitialized = true;\n this.refreshCount();\n }\n\n public Toggle(): void {\n this.PanelVisible = !this.PanelVisible;\n this.cdr.markForCheck();\n }\n\n public OnPanelClosed(): void {\n this.PanelVisible = false;\n // Refresh count — a restore may have removed an entry from the bin\n this.refreshCount();\n }\n\n /**\n * After-events bubble through but we also use them to refresh the count\n * — restoring a record means it's no longer \"deleted\".\n */\n public OnAfterRecycleBinOpen(e: AfterRecycleBinOpenEventArgs): void {\n this.DeletedCount = e.deletedRecordCount;\n this.AfterRecycleBinOpen.emit(e);\n this.cdr.markForCheck();\n }\n\n public OnAfterRestoreCommit(e: AfterRestoreCommitEventArgs): void {\n if (e.success) this.DeletedCount = Math.max(0, this.DeletedCount - 1);\n this.AfterRestoreCommit.emit(e);\n this.cdr.markForCheck();\n }\n\n /**\n * Loads the deleted-record count and visibility flags. Called on init,\n * when EntityName changes, and after the panel closes (so a restore\n * decrements the badge).\n */\n private async refreshCount(): Promise<void> {\n this.IsVisible = false;\n this.DeletedCount = 0;\n\n if (!this._entityName) {\n this.cdr.markForCheck();\n return;\n }\n\n const md = new Metadata();\n const entityInfo = md.Entities.find(\n e => e.Name.trim().toLowerCase() === this._entityName!.trim().toLowerCase(),\n );\n if (!entityInfo) {\n this.cdr.markForCheck();\n return;\n }\n\n this.TracksChanges = !!entityInfo.TrackRecordChanges;\n if (!this.TracksChanges) {\n this.cdr.markForCheck();\n return;\n }\n\n const effectiveUser = this.ContextUser ?? md.CurrentUser;\n if (!effectiveUser) {\n this.cdr.markForCheck();\n return;\n }\n const perms = entityInfo.GetUserPermisions(effectiveUser);\n this.CanDelete = perms?.CanDelete ?? false;\n if (!this.CanDelete) {\n this.cdr.markForCheck();\n return;\n }\n\n // Cheap count query — just IDs, capped low\n try {\n const rv = new RunView();\n const result = await rv.RunView<{ ID: string; RecordID: string }>({\n EntityName: 'MJ: Record Changes',\n Fields: ['ID', 'RecordID'],\n ExtraFilter: `EntityID='${entityInfo.ID}' AND Type='Delete'`,\n OrderBy: 'ChangedAt DESC',\n MaxRows: 500,\n ResultType: 'simple',\n }, this.ContextUser ?? undefined);\n\n if (result.Success) {\n // Distinct RecordIDs (a record might have been deleted, recreated, deleted again)\n const distinct = new Set(result.Results.map(r => r.RecordID));\n this.DeletedCount = distinct.size;\n }\n } catch {\n // Silently ignore — chip just won't show\n }\n\n this.IsVisible = this.DeletedCount > 0;\n this.cdr.markForCheck();\n }\n}\n"]}
@@ -0,0 +1,178 @@
1
+ import { ChangeDetectorRef, EventEmitter, OnInit } from '@angular/core';
2
+ import { UserInfo } from '@memberjunction/core';
3
+ import { RestoreCommitEvent } from '@memberjunction/ng-record-changes';
4
+ import { AfterRecordRestoreEventArgs, AfterRecycleBinOpenEventArgs, AfterRestoreCommitEventArgs, BeforeRecordRestoreEventArgs, BeforeRecycleBinOpenEventArgs, BeforeRestoreCommitEventArgs, RecycleBinEntry } from './events/recycle-bin-events';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * Slide-in panel that lists all hard-deleted records for a single entity
8
+ * and lets a user with `Delete` permission re-create any of them from its
9
+ * historical RecordChange snapshot.
10
+ *
11
+ * ### When to use
12
+ *
13
+ * Surface this component from any entity-viewing context where the user
14
+ * might need to restore a hard-deleted record. The {@link EntityViewerComponent},
15
+ * {@link EntityDataGridComponent}, and {@link EntityCardsComponent}
16
+ * already expose a `ShowRecycleBin` input (default `true`) that renders a
17
+ * chip in their toolbar — you only need to use this component directly if
18
+ * you're building a custom viewer.
19
+ *
20
+ * ### Permissions
21
+ *
22
+ * The chip / panel is gated on `entity.UserPermissions.CanDelete` —
23
+ * rationale: there is no native "undelete" permission in MemberJunction,
24
+ * but if a user has the higher-trust permission to *delete* records of an
25
+ * entity, restoring deleted ones is well within scope. The actual
26
+ * re-create action additionally requires `CanCreate`; without it the
27
+ * Restore button on each card disables with a tooltip.
28
+ *
29
+ * ### Soft deletes vs hard deletes
30
+ *
31
+ * This component only surfaces *hard*-deleted records. Soft-deletes (e.g.,
32
+ * `IsDeleted` flags, `Status='Inactive'`, etc.) leave the record visible
33
+ * in normal entity views, so the standard Record Changes panel + restore
34
+ * preview already handles them — no Recycle Bin needed.
35
+ *
36
+ * ### Cancelable Before/After event surface
37
+ *
38
+ * Every meaningful action emits a paired `before*` / `after*` event. The
39
+ * `before*` event carries `cancel: boolean` so consumers can intercept
40
+ * — useful for custom approval workflows, audit logging, or to take over
41
+ * the actual restore execution entirely. See {@link BeforeRecordRestoreEventArgs}
42
+ * and friends.
43
+ *
44
+ * @example Basic usage (rare — usually embedded by entity-viewer)
45
+ * <mj-recycle-bin
46
+ * [Visible]="showBin"
47
+ * [EntityName]="'Customers'"
48
+ * (Closed)="showBin = false"
49
+ * (BeforeRecordRestore)="auditRestore($event)"
50
+ * (AfterRestoreCommit)="onRestored($event)">
51
+ * </mj-recycle-bin>
52
+ *
53
+ * @example Intercepting a restore
54
+ * onBeforeRecordRestore(e: BeforeRecordRestoreEventArgs) {
55
+ * if (!myCustomApproval(e.entry)) {
56
+ * e.cancel = true;
57
+ * e.cancelReason = 'Awaiting compliance approval';
58
+ * }
59
+ * }
60
+ */
61
+ export declare class RecycleBinComponent implements OnInit {
62
+ private cdr;
63
+ /**
64
+ * Controls panel visibility. Set to true to slide in (and trigger an
65
+ * initial load); false to slide out.
66
+ */
67
+ private _visible;
68
+ set Visible(value: boolean);
69
+ get Visible(): boolean;
70
+ /**
71
+ * The name of the entity whose deleted records will be listed. Required.
72
+ */
73
+ EntityName: string | null;
74
+ /**
75
+ * Optional context user. When omitted, falls back to
76
+ * {@link Metadata.Provider.CurrentUser} per standard MJ conventions.
77
+ */
78
+ ContextUser: UserInfo | null;
79
+ /**
80
+ * Maximum number of deleted-record cards to load. Defaults to 200 — this
81
+ * is a UI affordance, not a hard limit; consumers needing pagination
82
+ * should listen to {@link AfterRecycleBinOpen} and surface their own UI
83
+ * if `deletedRecordCount === MaxRecords`.
84
+ */
85
+ MaxRecords: number;
86
+ /** Fires when the user closes the panel. */
87
+ Closed: EventEmitter<void>;
88
+ /**
89
+ * Cancelable. Fires when {@link Visible} flips to true and the bin is
90
+ * about to query for deleted records. Setting `cancel = true` aborts
91
+ * the query (and the panel will show the empty state).
92
+ */
93
+ BeforeRecycleBinOpen: EventEmitter<BeforeRecycleBinOpenEventArgs>;
94
+ /** Fires after the deleted-record query completes. */
95
+ AfterRecycleBinOpen: EventEmitter<AfterRecycleBinOpenEventArgs>;
96
+ /**
97
+ * Cancelable. Fires when the user clicks Restore on a deleted-record
98
+ * card, before the preview panel opens. Setting `cancel = true` skips
99
+ * the preview entirely — useful for consumers that want to take over
100
+ * the restore flow themselves.
101
+ */
102
+ BeforeRecordRestore: EventEmitter<BeforeRecordRestoreEventArgs>;
103
+ /**
104
+ * Fires after the user closes the restore preview, with `success`
105
+ * indicating whether the actual insert worked.
106
+ */
107
+ AfterRecordRestore: EventEmitter<AfterRecordRestoreEventArgs>;
108
+ /**
109
+ * Cancelable. Fires after the user clicks Restore in the preview but
110
+ * before the entity is inserted. Setting `cancel = true` aborts the
111
+ * insert.
112
+ */
113
+ BeforeRestoreCommit: EventEmitter<BeforeRestoreCommitEventArgs>;
114
+ /** Fires after the insert completes (success or failure). */
115
+ AfterRestoreCommit: EventEmitter<AfterRestoreCommitEventArgs>;
116
+ IsLoading: boolean;
117
+ LoadError: string | null;
118
+ Entries: RecycleBinEntry[];
119
+ /** The currently selected entry whose preview is open. */
120
+ SelectedEntry: RecycleBinEntry | null;
121
+ PreviewVisible: boolean;
122
+ /** Permission flags (computed once when EntityName is set). */
123
+ CanDelete: boolean;
124
+ CanCreate: boolean;
125
+ private isInitialized;
126
+ private resolvedEntityInfo;
127
+ constructor(cdr: ChangeDetectorRef);
128
+ ngOnInit(): void;
129
+ get HeaderSubtitle(): string;
130
+ /**
131
+ * The user has Delete permission and may browse the bin. The Restore
132
+ * button on each card additionally requires {@link CanCreate}.
133
+ */
134
+ get HasAccess(): boolean;
135
+ /**
136
+ * Manually trigger a re-load — useful for consumers who want to refresh
137
+ * after some external event (e.g., a delete happened and they want the
138
+ * bin to re-populate immediately).
139
+ */
140
+ LoadDeletedRecords(): Promise<void>;
141
+ /**
142
+ * User clicked Restore on a card — open the preview panel (unless a
143
+ * BeforeRecordRestore handler cancelled).
144
+ */
145
+ OnRestoreClicked(entry: RecycleBinEntry): void;
146
+ /**
147
+ * Preview panel emitted RestoreConfirmed — perform the insert and emit
148
+ * the matching after* events.
149
+ */
150
+ OnPreviewConfirmed(commit: RestoreCommitEvent): Promise<void>;
151
+ OnPreviewCancelled(): void;
152
+ OnClose(): void;
153
+ /**
154
+ * Dedupes the raw delete-change rows: keeps only the most recent Delete
155
+ * per RecordID (since a record could in principle have been recreated
156
+ * and re-deleted). Builds the display fields heuristically by reading
157
+ * the snapshot JSON.
158
+ */
159
+ private dedupeAndBuildEntries;
160
+ /**
161
+ * Builds the headline of the card. Prefers the entity's name field, then
162
+ * any non-PK string field, then the RecordID itself as a last resort.
163
+ */
164
+ private buildDisplaySummary;
165
+ /**
166
+ * Picks up to 3 "interesting" fields from the snapshot to render as
167
+ * supporting metadata under the headline. Heuristic: prefer fields that
168
+ * are non-empty, non-PK, non-system, and have a display name.
169
+ */
170
+ private buildSupportingFields;
171
+ private formatFieldValue;
172
+ private parseSnapshot;
173
+ formatTimestamp(date: Date | string | null): string;
174
+ getUserDisplay(user: string | null): string;
175
+ static ɵfac: i0.ɵɵFactoryDeclaration<RecycleBinComponent, never>;
176
+ static ɵcmp: i0.ɵɵComponentDeclaration<RecycleBinComponent, "mj-recycle-bin", never, { "Visible": { "alias": "Visible"; "required": false; }; "EntityName": { "alias": "EntityName"; "required": false; }; "ContextUser": { "alias": "ContextUser"; "required": false; }; "MaxRecords": { "alias": "MaxRecords"; "required": false; }; }, { "Closed": "Closed"; "BeforeRecycleBinOpen": "BeforeRecycleBinOpen"; "AfterRecycleBinOpen": "AfterRecycleBinOpen"; "BeforeRecordRestore": "BeforeRecordRestore"; "AfterRecordRestore": "AfterRecordRestore"; "BeforeRestoreCommit": "BeforeRestoreCommit"; "AfterRestoreCommit": "AfterRestoreCommit"; }, never, never, false, never>;
177
+ }
178
+ //# sourceMappingURL=recycle-bin.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle-bin.component.d.ts","sourceRoot":"","sources":["../../../src/lib/recycle-bin/recycle-bin.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,YAAY,EAEZ,MAAM,EAGP,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,eAAe,EAChB,MAAM,6BAA6B,CAAC;;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAQa,mBAAoB,YAAW,MAAM;IA+FpC,OAAO,CAAC,GAAG;IA5FvB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAS;IACzB,IACI,OAAO,CAAC,KAAK,EAAE,OAAO,EAMzB;IACD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACM,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACM,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAE7C;;;;;OAKG;IACM,UAAU,SAAO;IAI1B,4CAA4C;IAClC,MAAM,qBAA4B;IAE5C;;;;OAIG;IACO,oBAAoB,8CAAqD;IAEnF,sDAAsD;IAC5C,mBAAmB,6CAAoD;IAEjF;;;;;OAKG;IACO,mBAAmB,6CAAoD;IAEjF;;;OAGG;IACO,kBAAkB,4CAAmD;IAE/E;;;;OAIG;IACO,mBAAmB,6CAAoD;IAEjF,6DAA6D;IACnD,kBAAkB,4CAAmD;IAIxE,SAAS,UAAS;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,OAAO,EAAE,eAAe,EAAE,CAAM;IAEvC,0DAA0D;IACnD,aAAa,EAAE,eAAe,GAAG,IAAI,CAAQ;IAC7C,cAAc,UAAS;IAE9B,+DAA+D;IACxD,SAAS,UAAS;IAClB,SAAS,UAAS;IAEzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAA2B;gBAEjC,GAAG,EAAE,iBAAiB;IAE1C,QAAQ,IAAI,IAAI;IAOhB,IAAW,cAAc,IAAI,MAAM,CAIlC;IAED;;;OAGG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAID;;;;OAIG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4FhD;;;OAGG;IACI,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAmBrD;;;OAGG;IACU,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+FnE,kBAAkB,IAAI,IAAI;IAM1B,OAAO,IAAI,IAAI;IAQtB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,aAAa;IAWd,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM;IAYnD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;yCArdvC,mBAAmB;2CAAnB,mBAAmB;CA0d/B"}