@memberjunction/ng-ui-components 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 (71) hide show
  1. package/dist/lib/button/button.scss +7 -3
  2. package/dist/lib/filter-chip/filter-chip.component.d.ts +31 -0
  3. package/dist/lib/filter-chip/filter-chip.component.d.ts.map +1 -0
  4. package/dist/lib/filter-chip/filter-chip.component.js +94 -0
  5. package/dist/lib/filter-chip/filter-chip.component.js.map +1 -0
  6. package/dist/lib/filter-field/filter-field.component.d.ts +22 -0
  7. package/dist/lib/filter-field/filter-field.component.d.ts.map +1 -0
  8. package/dist/lib/filter-field/filter-field.component.js +60 -0
  9. package/dist/lib/filter-field/filter-field.component.js.map +1 -0
  10. package/dist/lib/filter-panel/filter-panel.component.d.ts +69 -0
  11. package/dist/lib/filter-panel/filter-panel.component.d.ts.map +1 -0
  12. package/dist/lib/filter-panel/filter-panel.component.js +203 -0
  13. package/dist/lib/filter-panel/filter-panel.component.js.map +1 -0
  14. package/dist/lib/filter-popover/filter-popover.component.d.ts +38 -0
  15. package/dist/lib/filter-popover/filter-popover.component.d.ts.map +1 -0
  16. package/dist/lib/filter-popover/filter-popover.component.js +170 -0
  17. package/dist/lib/filter-popover/filter-popover.component.js.map +1 -0
  18. package/dist/lib/left-nav/left-nav-content.component.d.ts +53 -0
  19. package/dist/lib/left-nav/left-nav-content.component.d.ts.map +1 -0
  20. package/dist/lib/left-nav/left-nav-content.component.js +121 -0
  21. package/dist/lib/left-nav/left-nav-content.component.js.map +1 -0
  22. package/dist/lib/left-nav/left-nav.component.d.ts +137 -0
  23. package/dist/lib/left-nav/left-nav.component.d.ts.map +1 -0
  24. package/dist/lib/left-nav/left-nav.component.js +399 -0
  25. package/dist/lib/left-nav/left-nav.component.js.map +1 -0
  26. package/dist/lib/page-body/page-body.component.d.ts +56 -0
  27. package/dist/lib/page-body/page-body.component.d.ts.map +1 -0
  28. package/dist/lib/page-body/page-body.component.js +89 -0
  29. package/dist/lib/page-body/page-body.component.js.map +1 -0
  30. package/dist/lib/page-body-interior/page-body-interior.component.d.ts +62 -0
  31. package/dist/lib/page-body-interior/page-body-interior.component.d.ts.map +1 -0
  32. package/dist/lib/page-body-interior/page-body-interior.component.js +95 -0
  33. package/dist/lib/page-body-interior/page-body-interior.component.js.map +1 -0
  34. package/dist/lib/page-header/page-header.component.d.ts +43 -0
  35. package/dist/lib/page-header/page-header.component.d.ts.map +1 -0
  36. package/dist/lib/page-header/page-header.component.js +126 -0
  37. package/dist/lib/page-header/page-header.component.js.map +1 -0
  38. package/dist/lib/page-header/page-header.scss +160 -0
  39. package/dist/lib/page-header-interior/page-header-interior.component.d.ts +96 -0
  40. package/dist/lib/page-header-interior/page-header-interior.component.d.ts.map +1 -0
  41. package/dist/lib/page-header-interior/page-header-interior.component.js +176 -0
  42. package/dist/lib/page-header-interior/page-header-interior.component.js.map +1 -0
  43. package/dist/lib/page-layout/page-layout.component.d.ts +20 -0
  44. package/dist/lib/page-layout/page-layout.component.d.ts.map +1 -0
  45. package/dist/lib/page-layout/page-layout.component.js +30 -0
  46. package/dist/lib/page-layout/page-layout.component.js.map +1 -0
  47. package/dist/lib/page-search/page-search.component.d.ts +29 -0
  48. package/dist/lib/page-search/page-search.component.d.ts.map +1 -0
  49. package/dist/lib/page-search/page-search.component.js +69 -0
  50. package/dist/lib/page-search/page-search.component.js.map +1 -0
  51. package/dist/lib/refresh-button/refresh-button.component.d.ts +46 -0
  52. package/dist/lib/refresh-button/refresh-button.component.d.ts.map +1 -0
  53. package/dist/lib/refresh-button/refresh-button.component.js +106 -0
  54. package/dist/lib/refresh-button/refresh-button.component.js.map +1 -0
  55. package/dist/lib/stat-badge/stat-badge.component.d.ts +54 -0
  56. package/dist/lib/stat-badge/stat-badge.component.d.ts.map +1 -0
  57. package/dist/lib/stat-badge/stat-badge.component.js +162 -0
  58. package/dist/lib/stat-badge/stat-badge.component.js.map +1 -0
  59. package/dist/lib/tab-nav/tab-nav.component.d.ts +41 -0
  60. package/dist/lib/tab-nav/tab-nav.component.d.ts.map +1 -0
  61. package/dist/lib/tab-nav/tab-nav.component.js +111 -0
  62. package/dist/lib/tab-nav/tab-nav.component.js.map +1 -0
  63. package/dist/lib/view-toggle/view-toggle.component.d.ts +42 -0
  64. package/dist/lib/view-toggle/view-toggle.component.d.ts.map +1 -0
  65. package/dist/lib/view-toggle/view-toggle.component.js +104 -0
  66. package/dist/lib/view-toggle/view-toggle.component.js.map +1 -0
  67. package/dist/public-api.d.ts +16 -0
  68. package/dist/public-api.d.ts.map +1 -1
  69. package/dist/public-api.js +16 -0
  70. package/dist/public-api.js.map +1 -1
  71. package/package.json +2 -2
@@ -0,0 +1,170 @@
1
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
2
+ import { OverlayModule } from '@angular/cdk/overlay';
3
+ import * as i0 from "@angular/core";
4
+ import * as i1 from "@angular/cdk/overlay";
5
+ const _c0 = ["*"];
6
+ function MJFilterPopoverComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
7
+ i0.ɵɵelementStart(0, "span", 2);
8
+ i0.ɵɵtext(1);
9
+ i0.ɵɵelementEnd();
10
+ } if (rf & 2) {
11
+ const ctx_r1 = i0.ɵɵnextContext();
12
+ i0.ɵɵattribute("aria-label", ctx_r1.ActiveCount + " active filters");
13
+ i0.ɵɵadvance();
14
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.ActiveCount, " ");
15
+ } }
16
+ function MJFilterPopoverComponent_ng_template_7_Conditional_4_Template(rf, ctx) { if (rf & 1) {
17
+ const _r3 = i0.ɵɵgetCurrentView();
18
+ i0.ɵɵelementStart(0, "button", 10);
19
+ i0.ɵɵlistener("click", function MJFilterPopoverComponent_ng_template_7_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ClearAll()); });
20
+ i0.ɵɵtext(1, " Clear all ");
21
+ i0.ɵɵelementEnd();
22
+ } }
23
+ function MJFilterPopoverComponent_ng_template_7_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵelementStart(0, "div", 5)(1, "div", 6)(2, "span", 7);
25
+ i0.ɵɵtext(3);
26
+ i0.ɵɵelementEnd();
27
+ i0.ɵɵconditionalCreate(4, MJFilterPopoverComponent_ng_template_7_Conditional_4_Template, 2, 0, "button", 8);
28
+ i0.ɵɵelementEnd();
29
+ i0.ɵɵelementStart(5, "div", 9);
30
+ i0.ɵɵprojection(6);
31
+ i0.ɵɵelementEnd()();
32
+ } if (rf & 2) {
33
+ const ctx_r1 = i0.ɵɵnextContext();
34
+ i0.ɵɵattribute("aria-label", ctx_r1.Label);
35
+ i0.ɵɵadvance(3);
36
+ i0.ɵɵtextInterpolate(ctx_r1.Label);
37
+ i0.ɵɵadvance();
38
+ i0.ɵɵconditional(ctx_r1.ActiveCount > 0 && ctx_r1.ShowClearAll ? 4 : -1);
39
+ } }
40
+ /**
41
+ * mj-filter-popover — Compact "Filters" trigger button that opens a popover
42
+ * containing arbitrary filter UI. Use this in a dashboard's page-header to
43
+ * keep the header chrome consistent regardless of filter density.
44
+ *
45
+ * Pass the popover content via `<ng-content>`. Drive the active-count badge
46
+ * via the `ActiveCount` input.
47
+ *
48
+ * Example:
49
+ * ```html
50
+ * <mj-filter-popover [ActiveCount]="activeFilters.length">
51
+ * <div class="my-filter-grid">
52
+ * <mj-dropdown ...></mj-dropdown>
53
+ * <mj-dropdown ...></mj-dropdown>
54
+ * </div>
55
+ * </mj-filter-popover>
56
+ * ```
57
+ */
58
+ export class MJFilterPopoverComponent {
59
+ Label = 'Filters';
60
+ Icon = 'fa-solid fa-filter';
61
+ ActiveCount = 0;
62
+ ShowClearAll = false;
63
+ IsOpen = false;
64
+ // Note: NO offsetY here — CDK applies offsetY as `transform: translateY(...)` on the
65
+ // overlay pane, which creates a containing block for `position: fixed` descendants.
66
+ // Tree dropdowns (and any other component using position: fixed) would then mis-position.
67
+ // The 6px spacing below the trigger is applied via margin-top on .mj-filter-popover-panel.
68
+ Positions = [
69
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' },
70
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
71
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }
72
+ ];
73
+ Toggle() { this.IsOpen = !this.IsOpen; }
74
+ Open() { this.IsOpen = true; }
75
+ Close() { this.IsOpen = false; }
76
+ /** Emitted when the user clicks the "Clear all" link inside the popover header. */
77
+ ClearAllRequested = new EventEmitter();
78
+ ClearAll() {
79
+ this.ClearAllRequested.emit();
80
+ }
81
+ static ɵfac = function MJFilterPopoverComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJFilterPopoverComponent)(); };
82
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJFilterPopoverComponent, selectors: [["mj-filter-popover"]], inputs: { Label: "Label", Icon: "Icon", ActiveCount: "ActiveCount", ShowClearAll: "ShowClearAll" }, outputs: { ClearAllRequested: "ClearAllRequested" }, ngContentSelectors: _c0, decls: 8, vars: 14, consts: [["overlayOrigin", "cdkOverlayOrigin"], ["type", "button", "cdkOverlayOrigin", "", 1, "mj-filter-popover-trigger", 3, "click"], [1, "mj-filter-popover-badge"], [1, "fa-solid", "fa-chevron-down", "mj-filter-popover-chevron"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "mj-filter-popover-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayHasBackdrop"], ["role", "dialog", 1, "mj-filter-popover-panel"], [1, "mj-filter-popover-header"], [1, "mj-filter-popover-title"], ["type", "button", 1, "mj-filter-popover-clear"], [1, "mj-filter-popover-content"], ["type", "button", 1, "mj-filter-popover-clear", 3, "click"]], template: function MJFilterPopoverComponent_Template(rf, ctx) { if (rf & 1) {
83
+ const _r1 = i0.ɵɵgetCurrentView();
84
+ i0.ɵɵprojectionDef();
85
+ i0.ɵɵelementStart(0, "button", 1, 0);
86
+ i0.ɵɵlistener("click", function MJFilterPopoverComponent_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Toggle()); });
87
+ i0.ɵɵelement(2, "i");
88
+ i0.ɵɵelementStart(3, "span");
89
+ i0.ɵɵtext(4);
90
+ i0.ɵɵelementEnd();
91
+ i0.ɵɵconditionalCreate(5, MJFilterPopoverComponent_Conditional_5_Template, 2, 2, "span", 2);
92
+ i0.ɵɵelement(6, "i", 3);
93
+ i0.ɵɵelementEnd();
94
+ i0.ɵɵtemplate(7, MJFilterPopoverComponent_ng_template_7_Template, 7, 3, "ng-template", 4);
95
+ i0.ɵɵlistener("backdropClick", function MJFilterPopoverComponent_Template_ng_template_backdropClick_7_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Close()); })("detach", function MJFilterPopoverComponent_Template_ng_template_detach_7_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Close()); });
96
+ } if (rf & 2) {
97
+ const overlayOrigin_r4 = i0.ɵɵreference(1);
98
+ i0.ɵɵclassProp("mj-filter-popover-trigger--active", ctx.IsOpen || ctx.ActiveCount > 0);
99
+ i0.ɵɵattribute("aria-expanded", ctx.IsOpen)("aria-haspopup", true);
100
+ i0.ɵɵadvance(2);
101
+ i0.ɵɵclassMap(ctx.Icon);
102
+ i0.ɵɵadvance(2);
103
+ i0.ɵɵtextInterpolate(ctx.Label);
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵconditional(ctx.ActiveCount > 0 ? 5 : -1);
106
+ i0.ɵɵadvance();
107
+ i0.ɵɵclassProp("mj-filter-popover-chevron--open", ctx.IsOpen);
108
+ i0.ɵɵadvance();
109
+ i0.ɵɵproperty("cdkConnectedOverlayOrigin", overlayOrigin_r4)("cdkConnectedOverlayOpen", ctx.IsOpen)("cdkConnectedOverlayPositions", ctx.Positions)("cdkConnectedOverlayHasBackdrop", true);
110
+ } }, dependencies: [OverlayModule, i1.CdkConnectedOverlay, i1.CdkOverlayOrigin], styles: ["[_nghost-%COMP%] {\n display: inline-flex;\n }\n\n \n\n\n\n .mj-filter-popover-trigger[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n min-height: 32px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: var(--mj-font-semibold);\n line-height: 1.5;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n white-space: nowrap;\n }\n\n .mj-filter-popover-trigger[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-border-strong);\n }\n\n .mj-filter-popover-trigger--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-trigger--active[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n }\n\n .mj-filter-popover-trigger[_ngcontent-%COMP%] > i[_ngcontent-%COMP%]:first-child {\n font-size: var(--mj-text-xs);\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 var(--mj-space-1-5);\n border-radius: 10px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n }\n\n .mj-filter-popover-chevron[_ngcontent-%COMP%] {\n font-size: 10px;\n transition: transform 0.2s ease;\n color: var(--mj-text-muted);\n }\n\n .mj-filter-popover-chevron--open[_ngcontent-%COMP%] {\n transform: rotate(180deg);\n }\n\n .mj-filter-popover-panel[_ngcontent-%COMP%] {\n min-width: 320px;\n max-width: 480px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n margin-top: var(--mj-space-1-5);\n overflow: hidden;\n }\n\n .mj-filter-popover-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-3) var(--mj-space-4) 0;\n }\n\n .mj-filter-popover-title[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .mj-filter-popover-clear[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-xs);\n font-weight: 500;\n cursor: pointer;\n padding: var(--mj-space-1) var(--mj-space-2);\n border-radius: var(--mj-radius-sm);\n }\n\n .mj-filter-popover-clear[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .mj-filter-popover-content[_ngcontent-%COMP%] {\n padding: var(--mj-space-3) var(--mj-space-4) var(--mj-space-4);\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2-5);\n }"] });
111
+ }
112
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJFilterPopoverComponent, [{
113
+ type: Component,
114
+ args: [{ selector: 'mj-filter-popover', standalone: true, imports: [OverlayModule], template: `
115
+ <button
116
+ type="button"
117
+ class="mj-filter-popover-trigger"
118
+ [class.mj-filter-popover-trigger--active]="IsOpen || ActiveCount > 0"
119
+ cdkOverlayOrigin
120
+ #overlayOrigin="cdkOverlayOrigin"
121
+ [attr.aria-expanded]="IsOpen"
122
+ [attr.aria-haspopup]="true"
123
+ (click)="Toggle()">
124
+ <i [class]="Icon"></i>
125
+ <span>{{ Label }}</span>
126
+ @if (ActiveCount > 0) {
127
+ <span class="mj-filter-popover-badge" [attr.aria-label]="ActiveCount + ' active filters'">
128
+ {{ ActiveCount }}
129
+ </span>
130
+ }
131
+ <i class="fa-solid fa-chevron-down mj-filter-popover-chevron" [class.mj-filter-popover-chevron--open]="IsOpen"></i>
132
+ </button>
133
+
134
+ <ng-template
135
+ cdkConnectedOverlay
136
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
137
+ [cdkConnectedOverlayOpen]="IsOpen"
138
+ [cdkConnectedOverlayPositions]="Positions"
139
+ [cdkConnectedOverlayHasBackdrop]="true"
140
+ cdkConnectedOverlayBackdropClass="mj-filter-popover-backdrop"
141
+ (backdropClick)="Close()"
142
+ (detach)="Close()">
143
+ <div class="mj-filter-popover-panel" role="dialog" [attr.aria-label]="Label">
144
+ <div class="mj-filter-popover-header">
145
+ <span class="mj-filter-popover-title">{{ Label }}</span>
146
+ @if (ActiveCount > 0 && ShowClearAll) {
147
+ <button type="button" class="mj-filter-popover-clear" (click)="ClearAll()">
148
+ Clear all
149
+ </button>
150
+ }
151
+ </div>
152
+ <div class="mj-filter-popover-content">
153
+ <ng-content></ng-content>
154
+ </div>
155
+ </div>
156
+ </ng-template>
157
+ `, styles: ["\n :host {\n display: inline-flex;\n }\n\n /* Trigger button \u2014 visually aligned with mjButton size=\"sm\" variant=\"secondary\"\n (same height, padding, font-size, font-weight, background) so it sits\n cleanly next to Refresh/Export/+Add in chrome and filter-card rows. */\n .mj-filter-popover-trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n min-height: 32px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: var(--mj-font-semibold);\n line-height: 1.5;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n white-space: nowrap;\n }\n\n .mj-filter-popover-trigger:hover {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-border-strong);\n }\n\n .mj-filter-popover-trigger--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-trigger--active:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n }\n\n .mj-filter-popover-trigger > i:first-child {\n font-size: var(--mj-text-xs);\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 var(--mj-space-1-5);\n border-radius: 10px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n }\n\n .mj-filter-popover-chevron {\n font-size: 10px;\n transition: transform 0.2s ease;\n color: var(--mj-text-muted);\n }\n\n .mj-filter-popover-chevron--open {\n transform: rotate(180deg);\n }\n\n .mj-filter-popover-panel {\n min-width: 320px;\n max-width: 480px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n margin-top: var(--mj-space-1-5);\n overflow: hidden;\n }\n\n .mj-filter-popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-3) var(--mj-space-4) 0;\n }\n\n .mj-filter-popover-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .mj-filter-popover-clear {\n background: transparent;\n border: none;\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-xs);\n font-weight: 500;\n cursor: pointer;\n padding: var(--mj-space-1) var(--mj-space-2);\n border-radius: var(--mj-radius-sm);\n }\n\n .mj-filter-popover-clear:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .mj-filter-popover-content {\n padding: var(--mj-space-3) var(--mj-space-4) var(--mj-space-4);\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2-5);\n }\n "] }]
158
+ }], null, { Label: [{
159
+ type: Input
160
+ }], Icon: [{
161
+ type: Input
162
+ }], ActiveCount: [{
163
+ type: Input
164
+ }], ShowClearAll: [{
165
+ type: Input
166
+ }], ClearAllRequested: [{
167
+ type: Output
168
+ }] }); })();
169
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJFilterPopoverComponent, { className: "MJFilterPopoverComponent", filePath: "lib/filter-popover/filter-popover.component.ts", lineNumber: 189 }); })();
170
+ //# sourceMappingURL=filter-popover.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-popover.component.js","sourceRoot":"","sources":["../../../src/lib/filter-popover/filter-popover.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;;;;;IAqChE,+BAA0F;IACxF,YACF;IAAA,iBAAO;;;;IADL,cACF;IADE,mDACF;;;;IAkBI,kCAA2E;IAArB,2MAAS,iBAAU,KAAC;IACxE,2BACF;IAAA,iBAAS;;;IAJX,AADF,AADF,8BAA6E,aACrC,cACE;IAAA,YAAW;IAAA,iBAAO;IACxD,2GAAuC;IAKzC,iBAAM;IACN,8BAAuC;IACrC,kBAAyB;IAE7B,AADE,iBAAM,EACF;;;;IAVoC,eAAW;IAAX,kCAAW;IACjD,cAIC;IAJD,wEAIC;;AA1DX;;;;;;;;;;;;;;;;;GAiBG;AAwKH,MAAM,OAAO,wBAAwB;IAC1B,KAAK,GAAW,SAAS,CAAC;IAC1B,IAAI,GAAW,oBAAoB,CAAC;IACpC,WAAW,GAAW,CAAC,CAAC;IACxB,YAAY,GAAY,KAAK,CAAC;IAEhC,MAAM,GAAY,KAAK,CAAC;IAE/B,qFAAqF;IACrF,oFAAoF;IACpF,0FAA0F;IAC1F,2FAA2F;IAC3E,SAAS,GAAwB;QAC/C,EAAE,OAAO,EAAE,KAAK,EAAI,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAI,QAAQ,EAAE,KAAK,EAAE;QAC3E,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3E,EAAE,OAAO,EAAE,KAAK,EAAI,OAAO,EAAE,KAAK,EAAK,QAAQ,EAAE,KAAK,EAAI,QAAQ,EAAE,QAAQ,EAAE;KAC/E,CAAC;IAEK,MAAM,KAAW,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,IAAI,KAAW,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACpC,KAAK,KAAW,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;IAE7C,mFAAmF;IAClE,iBAAiB,GAAG,IAAI,YAAY,EAAQ,CAAC;IAEvD,QAAQ;QACb,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;kHA3BU,wBAAwB;6DAAxB,wBAAwB;;;YAlKjC,oCAQqB;YAAnB,2IAAS,YAAQ,KAAC;YAClB,oBAAsB;YACtB,4BAAM;YAAA,YAAW;YAAA,iBAAO;YACxB,2FAAuB;YAKvB,uBAAmH;YACrH,iBAAS;YAET,yFAQqB;YAAnB,AADA,gKAAiB,WAAO,KAAC,qIACf,WAAO,KAAC;;;YAxBlB,sFAAqE;;YAMlE,eAAc;YAAd,uBAAc;YACX,eAAW;YAAX,+BAAW;YACjB,cAIC;YAJD,8CAIC;YAC6D,cAAgD;YAAhD,6DAAgD;YAK9G,cAA2C;YAG3C,AADA,AADA,AADA,4DAA2C,uCACT,+CACQ,wCACH;4BA1BjC,aAAa;;iFAoKZ,wBAAwB;cAvKpC,SAAS;2BACE,mBAAmB,cACjB,IAAI,WACP,CAAC,aAAa,CAAC,YACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CT;;kBAyHA,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBAmBL,MAAM;;kFAvBI,wBAAwB","sourcesContent":["import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { OverlayModule, ConnectedPosition } from '@angular/cdk/overlay';\n\n/**\n * mj-filter-popover — Compact \"Filters\" trigger button that opens a popover\n * containing arbitrary filter UI. Use this in a dashboard's page-header to\n * keep the header chrome consistent regardless of filter density.\n *\n * Pass the popover content via `<ng-content>`. Drive the active-count badge\n * via the `ActiveCount` input.\n *\n * Example:\n * ```html\n * <mj-filter-popover [ActiveCount]=\"activeFilters.length\">\n * <div class=\"my-filter-grid\">\n * <mj-dropdown ...></mj-dropdown>\n * <mj-dropdown ...></mj-dropdown>\n * </div>\n * </mj-filter-popover>\n * ```\n */\n@Component({\n selector: 'mj-filter-popover',\n standalone: true,\n imports: [OverlayModule],\n template: `\n <button\n type=\"button\"\n class=\"mj-filter-popover-trigger\"\n [class.mj-filter-popover-trigger--active]=\"IsOpen || ActiveCount > 0\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n [attr.aria-expanded]=\"IsOpen\"\n [attr.aria-haspopup]=\"true\"\n (click)=\"Toggle()\">\n <i [class]=\"Icon\"></i>\n <span>{{ Label }}</span>\n @if (ActiveCount > 0) {\n <span class=\"mj-filter-popover-badge\" [attr.aria-label]=\"ActiveCount + ' active filters'\">\n {{ ActiveCount }}\n </span>\n }\n <i class=\"fa-solid fa-chevron-down mj-filter-popover-chevron\" [class.mj-filter-popover-chevron--open]=\"IsOpen\"></i>\n </button>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"IsOpen\"\n [cdkConnectedOverlayPositions]=\"Positions\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"mj-filter-popover-backdrop\"\n (backdropClick)=\"Close()\"\n (detach)=\"Close()\">\n <div class=\"mj-filter-popover-panel\" role=\"dialog\" [attr.aria-label]=\"Label\">\n <div class=\"mj-filter-popover-header\">\n <span class=\"mj-filter-popover-title\">{{ Label }}</span>\n @if (ActiveCount > 0 && ShowClearAll) {\n <button type=\"button\" class=\"mj-filter-popover-clear\" (click)=\"ClearAll()\">\n Clear all\n </button>\n }\n </div>\n <div class=\"mj-filter-popover-content\">\n <ng-content></ng-content>\n </div>\n </div>\n </ng-template>\n `,\n styles: [`\n :host {\n display: inline-flex;\n }\n\n /* Trigger button — visually aligned with mjButton size=\"sm\" variant=\"secondary\"\n (same height, padding, font-size, font-weight, background) so it sits\n cleanly next to Refresh/Export/+Add in chrome and filter-card rows. */\n .mj-filter-popover-trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n min-height: 32px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: var(--mj-font-semibold);\n line-height: 1.5;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n white-space: nowrap;\n }\n\n .mj-filter-popover-trigger:hover {\n background: var(--mj-bg-surface-active);\n border-color: var(--mj-border-strong);\n }\n\n .mj-filter-popover-trigger--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-trigger--active:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface));\n }\n\n .mj-filter-popover-trigger > i:first-child {\n font-size: var(--mj-text-xs);\n color: var(--mj-brand-primary);\n }\n\n .mj-filter-popover-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 var(--mj-space-1-5);\n border-radius: 10px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n }\n\n .mj-filter-popover-chevron {\n font-size: 10px;\n transition: transform 0.2s ease;\n color: var(--mj-text-muted);\n }\n\n .mj-filter-popover-chevron--open {\n transform: rotate(180deg);\n }\n\n .mj-filter-popover-panel {\n min-width: 320px;\n max-width: 480px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n margin-top: var(--mj-space-1-5);\n overflow: hidden;\n }\n\n .mj-filter-popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mj-space-3) var(--mj-space-4) 0;\n }\n\n .mj-filter-popover-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .mj-filter-popover-clear {\n background: transparent;\n border: none;\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-xs);\n font-weight: 500;\n cursor: pointer;\n padding: var(--mj-space-1) var(--mj-space-2);\n border-radius: var(--mj-radius-sm);\n }\n\n .mj-filter-popover-clear:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .mj-filter-popover-content {\n padding: var(--mj-space-3) var(--mj-space-4) var(--mj-space-4);\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2-5);\n }\n `]\n})\nexport class MJFilterPopoverComponent {\n @Input() Label: string = 'Filters';\n @Input() Icon: string = 'fa-solid fa-filter';\n @Input() ActiveCount: number = 0;\n @Input() ShowClearAll: boolean = false;\n\n public IsOpen: boolean = false;\n\n // Note: NO offsetY here — CDK applies offsetY as `transform: translateY(...)` on the\n // overlay pane, which creates a containing block for `position: fixed` descendants.\n // Tree dropdowns (and any other component using position: fixed) would then mis-position.\n // The 6px spacing below the trigger is applied via margin-top on .mj-filter-popover-panel.\n public readonly Positions: ConnectedPosition[] = [\n { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' },\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }\n ];\n\n public Toggle(): void { this.IsOpen = !this.IsOpen; }\n public Open(): void { this.IsOpen = true; }\n public Close(): void { this.IsOpen = false; }\n\n /** Emitted when the user clicks the \"Clear all\" link inside the popover header. */\n @Output() public ClearAllRequested = new EventEmitter<void>();\n\n public ClearAll(): void {\n this.ClearAllRequested.emit();\n }\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * `<mj-left-nav-content>` — Content pane paired with `<mj-left-nav>`.
4
+ *
5
+ * Sits to the right of `<mj-left-nav>` inside `<mj-page-body [Flex]="true"
6
+ * Direction="row">` and holds the active section's content. Provides the
7
+ * standard layout box (`flex: 1`, page background, vertical overflow control)
8
+ * plus built-in `[Loading]` / `[Error]` states so each shell doesn't reinvent
9
+ * them.
10
+ *
11
+ * Projected content stays attached but hides via CSS when loading or errored —
12
+ * matches the cached-component pattern used by admin shells where dynamically
13
+ * loaded sub-pages should retain their state across section switches.
14
+ *
15
+ * ## Example
16
+ *
17
+ * ```html
18
+ * <mj-page-body [Flex]="true" [Padding]="false" Direction="row">
19
+ * <mj-left-nav [Sections]="NavSections" [ActiveId]="ActiveSection"
20
+ * (ItemClicked)="OnNavItemClicked($event)" />
21
+ * <mj-left-nav-content [Loading]="IsLoading" [Error]="LoadError">
22
+ * <ng-container #contentHost></ng-container>
23
+ * </mj-left-nav-content>
24
+ * </mj-page-body>
25
+ * ```
26
+ *
27
+ * Replaces the bespoke `.{shell}-container__content` + `.{shell}-container__host`
28
+ * + `.{shell}-container__loading` + `.{shell}-container__error` pattern that
29
+ * every left-nav shell used to declare independently.
30
+ */
31
+ export declare class MJLeftNavContentComponent {
32
+ /**
33
+ * When `true`, shows a centered loading state (spinner + label) and hides
34
+ * any projected content via CSS. Cached components remain attached.
35
+ */
36
+ Loading: boolean;
37
+ /**
38
+ * When non-empty, shows a centered error state with this message and hides
39
+ * any projected content.
40
+ */
41
+ Error: string | null;
42
+ /** Label shown in the loading state. Defaults to "Loading…". */
43
+ LoadingLabel: string;
44
+ /**
45
+ * Drives the busy-state class on the host. When set, CSS hides projected
46
+ * content (cached components stay attached, just not visible).
47
+ */
48
+ get IsBusy(): boolean;
49
+ get AriaBusy(): string | null;
50
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJLeftNavContentComponent, never>;
51
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJLeftNavContentComponent, "mj-left-nav-content", never, { "Loading": { "alias": "Loading"; "required": false; }; "Error": { "alias": "Error"; "required": false; }; "LoadingLabel": { "alias": "LoadingLabel"; "required": false; }; }, {}, never, ["*"], true, never>;
52
+ }
53
+ //# sourceMappingURL=left-nav-content.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"left-nav-content.component.d.ts","sourceRoot":"","sources":["../../../src/lib/left-nav/left-nav-content.component.ts"],"names":[],"mappings":";AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAiFa,yBAAyB;IACpC;;;OAGG;IACM,OAAO,EAAE,OAAO,CAAS;IAElC;;;OAGG;IACM,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC,gEAAgE;IACvD,YAAY,EAAE,MAAM,CAAc;IAE3C;;;OAGG;IACH,IACI,MAAM,IAAI,OAAO,CAEpB;IAED,IACI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;yCA5BU,yBAAyB;2CAAzB,yBAAyB;CA6BrC"}
@@ -0,0 +1,121 @@
1
+ import { Component, HostBinding, Input } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from "@angular/core";
4
+ const _c0 = ["*"];
5
+ function MJLeftNavContentComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
6
+ i0.ɵɵdomElementStart(0, "div", 0);
7
+ i0.ɵɵdomElement(1, "i", 2);
8
+ i0.ɵɵdomElementStart(2, "span");
9
+ i0.ɵɵtext(3);
10
+ i0.ɵɵdomElementEnd()();
11
+ } if (rf & 2) {
12
+ const ctx_r0 = i0.ɵɵnextContext();
13
+ i0.ɵɵadvance(3);
14
+ i0.ɵɵtextInterpolate(ctx_r0.Error);
15
+ } }
16
+ function MJLeftNavContentComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
17
+ i0.ɵɵdomElementStart(0, "div", 1);
18
+ i0.ɵɵdomElement(1, "i", 3);
19
+ i0.ɵɵdomElementStart(2, "span");
20
+ i0.ɵɵtext(3);
21
+ i0.ɵɵdomElementEnd()();
22
+ } if (rf & 2) {
23
+ const ctx_r0 = i0.ɵɵnextContext();
24
+ i0.ɵɵadvance(3);
25
+ i0.ɵɵtextInterpolate(ctx_r0.LoadingLabel);
26
+ } }
27
+ /**
28
+ * `<mj-left-nav-content>` — Content pane paired with `<mj-left-nav>`.
29
+ *
30
+ * Sits to the right of `<mj-left-nav>` inside `<mj-page-body [Flex]="true"
31
+ * Direction="row">` and holds the active section's content. Provides the
32
+ * standard layout box (`flex: 1`, page background, vertical overflow control)
33
+ * plus built-in `[Loading]` / `[Error]` states so each shell doesn't reinvent
34
+ * them.
35
+ *
36
+ * Projected content stays attached but hides via CSS when loading or errored —
37
+ * matches the cached-component pattern used by admin shells where dynamically
38
+ * loaded sub-pages should retain their state across section switches.
39
+ *
40
+ * ## Example
41
+ *
42
+ * ```html
43
+ * <mj-page-body [Flex]="true" [Padding]="false" Direction="row">
44
+ * <mj-left-nav [Sections]="NavSections" [ActiveId]="ActiveSection"
45
+ * (ItemClicked)="OnNavItemClicked($event)" />
46
+ * <mj-left-nav-content [Loading]="IsLoading" [Error]="LoadError">
47
+ * <ng-container #contentHost></ng-container>
48
+ * </mj-left-nav-content>
49
+ * </mj-page-body>
50
+ * ```
51
+ *
52
+ * Replaces the bespoke `.{shell}-container__content` + `.{shell}-container__host`
53
+ * + `.{shell}-container__loading` + `.{shell}-container__error` pattern that
54
+ * every left-nav shell used to declare independently.
55
+ */
56
+ export class MJLeftNavContentComponent {
57
+ /**
58
+ * When `true`, shows a centered loading state (spinner + label) and hides
59
+ * any projected content via CSS. Cached components remain attached.
60
+ */
61
+ Loading = false;
62
+ /**
63
+ * When non-empty, shows a centered error state with this message and hides
64
+ * any projected content.
65
+ */
66
+ Error = null;
67
+ /** Label shown in the loading state. Defaults to "Loading…". */
68
+ LoadingLabel = 'Loading…';
69
+ /**
70
+ * Drives the busy-state class on the host. When set, CSS hides projected
71
+ * content (cached components stay attached, just not visible).
72
+ */
73
+ get IsBusy() {
74
+ return this.Loading || !!this.Error;
75
+ }
76
+ get AriaBusy() {
77
+ return this.Loading ? 'true' : null;
78
+ }
79
+ static ɵfac = function MJLeftNavContentComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJLeftNavContentComponent)(); };
80
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJLeftNavContentComponent, selectors: [["mj-left-nav-content"]], hostVars: 3, hostBindings: function MJLeftNavContentComponent_HostBindings(rf, ctx) { if (rf & 2) {
81
+ i0.ɵɵattribute("aria-busy", ctx.AriaBusy);
82
+ i0.ɵɵclassProp("mj-left-nav-content--busy", ctx.IsBusy);
83
+ } }, inputs: { Loading: "Loading", Error: "Error", LoadingLabel: "LoadingLabel" }, ngContentSelectors: _c0, decls: 3, vars: 1, consts: [["role", "alert", 1, "mj-left-nav-content__error"], ["role", "status", "aria-live", "polite", 1, "mj-left-nav-content__loading"], ["aria-hidden", "true", 1, "fa-solid", "fa-triangle-exclamation"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"]], template: function MJLeftNavContentComponent_Template(rf, ctx) { if (rf & 1) {
84
+ i0.ɵɵprojectionDef();
85
+ i0.ɵɵconditionalCreate(0, MJLeftNavContentComponent_Conditional_0_Template, 4, 1, "div", 0)(1, MJLeftNavContentComponent_Conditional_1_Template, 4, 1, "div", 1);
86
+ i0.ɵɵprojection(2);
87
+ } if (rf & 2) {
88
+ i0.ɵɵconditional(ctx.Error ? 0 : ctx.Loading ? 1 : -1);
89
+ } }, dependencies: [CommonModule], styles: ["[_nghost-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n }\n\n \n\n\n\n\n\n\n\n\n\n\n\n [_nghost-%COMP%] > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading):not(mj-page-header-interior):not(mj-page-body-interior) {\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n \n\n\n .mj-left-nav-content--busy[_nghost-%COMP%] > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading) {\n display: none;\n }\n\n .mj-left-nav-content__error[_ngcontent-%COMP%], \n .mj-left-nav-content__loading[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n color: var(--mj-text-muted);\n font-size: 13px;\n padding: 32px;\n text-align: center;\n }\n\n .mj-left-nav-content__error[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n\n .mj-left-nav-content__error[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .mj-left-nav-content__loading[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n color: var(--mj-brand-primary);\n }"] });
90
+ }
91
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJLeftNavContentComponent, [{
92
+ type: Component,
93
+ args: [{ selector: 'mj-left-nav-content', standalone: true, imports: [CommonModule], template: `
94
+ @if (Error) {
95
+ <div class="mj-left-nav-content__error" role="alert">
96
+ <i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>
97
+ <span>{{ Error }}</span>
98
+ </div>
99
+ } @else if (Loading) {
100
+ <div class="mj-left-nav-content__loading" role="status" aria-live="polite">
101
+ <i class="fa-solid fa-spinner fa-spin" aria-hidden="true"></i>
102
+ <span>{{ LoadingLabel }}</span>
103
+ </div>
104
+ }
105
+ <ng-content></ng-content>
106
+ `, styles: ["\n :host {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n }\n\n /* Force dynamically-projected components (via ViewContainerRef.createComponent\n or ng-content) to fill the host with a proper flex-column layout. ::ng-deep\n is required because Angular's emulated encapsulation gives the host a scope\n attribute that dynamically-created components do not inherit, so a plain\n direct-child selector would not match them. The :not() filters skip our own\n error / loading elements AND the interior chrome primitives, which already\n self-declare their flex behavior on :host (header-interior is display:block\n with natural height; body-interior is flex:1 1 auto with overflow-y:auto)\n and competing with them would break multi-sibling chrome layouts inside a\n single content pane. This replaces the bespoke inline-style hack admin\n shells used to apply via applyHostSizing(). */\n :host ::ng-deep > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading):not(mj-page-header-interior):not(mj-page-body-interior) {\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n /* When busy, hide projected content but keep cached components attached\n to the DOM so their state survives the loading / error transition. */\n :host(.mj-left-nav-content--busy) ::ng-deep > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading) {\n display: none;\n }\n\n .mj-left-nav-content__error,\n .mj-left-nav-content__loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n color: var(--mj-text-muted);\n font-size: 13px;\n padding: 32px;\n text-align: center;\n }\n\n .mj-left-nav-content__error {\n color: var(--mj-status-error);\n }\n\n .mj-left-nav-content__error i {\n font-size: 20px;\n }\n\n .mj-left-nav-content__loading i {\n font-size: 16px;\n color: var(--mj-brand-primary);\n }\n "] }]
107
+ }], null, { Loading: [{
108
+ type: Input
109
+ }], Error: [{
110
+ type: Input
111
+ }], LoadingLabel: [{
112
+ type: Input
113
+ }], IsBusy: [{
114
+ type: HostBinding,
115
+ args: ['class.mj-left-nav-content--busy']
116
+ }], AriaBusy: [{
117
+ type: HostBinding,
118
+ args: ['attr.aria-busy']
119
+ }] }); })();
120
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJLeftNavContentComponent, { className: "MJLeftNavContentComponent", filePath: "lib/left-nav/left-nav-content.component.ts", lineNumber: 114 }); })();
121
+ //# sourceMappingURL=left-nav-content.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"left-nav-content.component.js","sourceRoot":"","sources":["../../../src/lib/left-nav/left-nav-content.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;;;;IAqCzC,iCAAqD;IACnD,0BAAmE;IACnE,+BAAM;IAAA,YAAW;IACnB,AADmB,oBAAO,EACpB;;;IADE,eAAW;IAAX,kCAAW;;;IAGnB,iCAA2E;IACzE,0BAA8D;IAC9D,+BAAM;IAAA,YAAkB;IAC1B,AAD0B,oBAAO,EAC3B;;;IADE,eAAkB;IAAlB,yCAAkB;;AA1ChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAkFH,MAAM,OAAO,yBAAyB;IACpC;;;OAGG;IACM,OAAO,GAAY,KAAK,CAAC;IAElC;;;OAGG;IACM,KAAK,GAAkB,IAAI,CAAC;IAErC,gEAAgE;IACvD,YAAY,GAAW,UAAU,CAAC;IAE3C;;;OAGG;IACH,IACI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IACtC,CAAC;IAED,IACI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC;mHA5BU,yBAAyB;6DAAzB,yBAAyB;;YAAzB,uDAAyB;;;YAvEhC,AALF,2FAAa,qEAKS;YAMtB,kBAAyB;;YAXzB,sDAUC;4BAZO,YAAY;;iFA8EX,yBAAyB;cAjFrC,SAAS;2BACE,qBAAqB,cACnB,IAAI,WACP,CAAC,YAAY,CAAC,YACb;;;;;;;;;;;;;GAaT;;kBAqEA,KAAK;;kBAML,KAAK;;kBAGL,KAAK;;kBAML,WAAW;mBAAC,iCAAiC;;kBAK7C,WAAW;mBAAC,gBAAgB;;kFAzBlB,yBAAyB","sourcesContent":["import { Component, HostBinding, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * `<mj-left-nav-content>` — Content pane paired with `<mj-left-nav>`.\n *\n * Sits to the right of `<mj-left-nav>` inside `<mj-page-body [Flex]=\"true\"\n * Direction=\"row\">` and holds the active section's content. Provides the\n * standard layout box (`flex: 1`, page background, vertical overflow control)\n * plus built-in `[Loading]` / `[Error]` states so each shell doesn't reinvent\n * them.\n *\n * Projected content stays attached but hides via CSS when loading or errored —\n * matches the cached-component pattern used by admin shells where dynamically\n * loaded sub-pages should retain their state across section switches.\n *\n * ## Example\n *\n * ```html\n * <mj-page-body [Flex]=\"true\" [Padding]=\"false\" Direction=\"row\">\n * <mj-left-nav [Sections]=\"NavSections\" [ActiveId]=\"ActiveSection\"\n * (ItemClicked)=\"OnNavItemClicked($event)\" />\n * <mj-left-nav-content [Loading]=\"IsLoading\" [Error]=\"LoadError\">\n * <ng-container #contentHost></ng-container>\n * </mj-left-nav-content>\n * </mj-page-body>\n * ```\n *\n * Replaces the bespoke `.{shell}-container__content` + `.{shell}-container__host`\n * + `.{shell}-container__loading` + `.{shell}-container__error` pattern that\n * every left-nav shell used to declare independently.\n */\n@Component({\n selector: 'mj-left-nav-content',\n standalone: true,\n imports: [CommonModule],\n template: `\n @if (Error) {\n <div class=\"mj-left-nav-content__error\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <span>{{ Error }}</span>\n </div>\n } @else if (Loading) {\n <div class=\"mj-left-nav-content__loading\" role=\"status\" aria-live=\"polite\">\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n <span>{{ LoadingLabel }}</span>\n </div>\n }\n <ng-content></ng-content>\n `,\n styles: [`\n :host {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n }\n\n /* Force dynamically-projected components (via ViewContainerRef.createComponent\n or ng-content) to fill the host with a proper flex-column layout. ::ng-deep\n is required because Angular's emulated encapsulation gives the host a scope\n attribute that dynamically-created components do not inherit, so a plain\n direct-child selector would not match them. The :not() filters skip our own\n error / loading elements AND the interior chrome primitives, which already\n self-declare their flex behavior on :host (header-interior is display:block\n with natural height; body-interior is flex:1 1 auto with overflow-y:auto)\n and competing with them would break multi-sibling chrome layouts inside a\n single content pane. This replaces the bespoke inline-style hack admin\n shells used to apply via applyHostSizing(). */\n :host ::ng-deep > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading):not(mj-page-header-interior):not(mj-page-body-interior) {\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n /* When busy, hide projected content but keep cached components attached\n to the DOM so their state survives the loading / error transition. */\n :host(.mj-left-nav-content--busy) ::ng-deep > *:not(.mj-left-nav-content__error):not(.mj-left-nav-content__loading) {\n display: none;\n }\n\n .mj-left-nav-content__error,\n .mj-left-nav-content__loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n color: var(--mj-text-muted);\n font-size: 13px;\n padding: 32px;\n text-align: center;\n }\n\n .mj-left-nav-content__error {\n color: var(--mj-status-error);\n }\n\n .mj-left-nav-content__error i {\n font-size: 20px;\n }\n\n .mj-left-nav-content__loading i {\n font-size: 16px;\n color: var(--mj-brand-primary);\n }\n `]\n})\nexport class MJLeftNavContentComponent {\n /**\n * When `true`, shows a centered loading state (spinner + label) and hides\n * any projected content via CSS. Cached components remain attached.\n */\n @Input() Loading: boolean = false;\n\n /**\n * When non-empty, shows a centered error state with this message and hides\n * any projected content.\n */\n @Input() Error: string | null = null;\n\n /** Label shown in the loading state. Defaults to \"Loading…\". */\n @Input() LoadingLabel: string = 'Loading…';\n\n /**\n * Drives the busy-state class on the host. When set, CSS hides projected\n * content (cached components stay attached, just not visible).\n */\n @HostBinding('class.mj-left-nav-content--busy')\n get IsBusy(): boolean {\n return this.Loading || !!this.Error;\n }\n\n @HostBinding('attr.aria-busy')\n get AriaBusy(): string | null {\n return this.Loading ? 'true' : null;\n }\n}\n"]}
@@ -0,0 +1,137 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * A single item in an `<mj-left-nav>`.
5
+ */
6
+ export interface MJLeftNavItem {
7
+ /** Stable identifier — used for active-state matching and emitted on click. */
8
+ id: string;
9
+ /** Primary label rendered as the item's main text. */
10
+ label: string;
11
+ /** Optional Font Awesome icon class (e.g. `'fa-solid fa-users'`). */
12
+ icon?: string;
13
+ /** Optional muted secondary line shown under the label. */
14
+ description?: string;
15
+ /** Optional count badge rendered at the right of the item. */
16
+ badge?: string | number;
17
+ /** When `true`, the item renders dimmed and is not clickable. */
18
+ disabled?: boolean;
19
+ /**
20
+ * Optional child items for tree-structured nav (e.g. Testing Explorer's
21
+ * Test Suites tree). When `children` is `undefined` the item renders as a
22
+ * plain flat-list item (existing behavior). When defined — even as an empty
23
+ * array — the item participates in tree rendering: a chevron toggle shows
24
+ * for non-empty arrays, and an equal-width placeholder reserves alignment
25
+ * space for leaves so siblings line up. Expansion state is owned by the
26
+ * consumer via `[ExpandedIds]` on `<mj-left-nav>`.
27
+ */
28
+ children?: MJLeftNavItem[];
29
+ }
30
+ /**
31
+ * A grouped section in an `<mj-left-nav>`. The optional `label` renders as an
32
+ * uppercase section header above the items. Sections WITHOUT a label still
33
+ * group items together (use multiple unlabeled sections to get visual dividers
34
+ * without headers).
35
+ */
36
+ export interface MJLeftNavSection {
37
+ /** Optional uppercase header above the items in this section. */
38
+ label?: string;
39
+ /** The clickable items in this section. */
40
+ items: MJLeftNavItem[];
41
+ }
42
+ /**
43
+ * `<mj-left-nav>` — Canonical left rail for MJ Explorer dashboards that have
44
+ * an internal section-nav (Admin shells, KH Config, KH Analytics, AI Analytics,
45
+ * Communication, Credentials, APIKeys, etc.).
46
+ *
47
+ * Replaces the bespoke `.{name}-nav` / `.{name}-nav-item` patterns each of
48
+ * those dashboards used to declare in their own CSS files.
49
+ *
50
+ * Supports:
51
+ * - Plain items (icon + label)
52
+ * - Items with a muted secondary description line (Admin shells pattern)
53
+ * - Items with a count badge (APIKeys pattern)
54
+ * - Sections with uppercase headers (Communication / Credentials pattern)
55
+ * - Optional `[header]` and `[footer]` content slots (KH Config logo header, etc.)
56
+ * - Responsive collapse-to-row at narrow viewports (consumer controls parent's
57
+ * flex-direction switch; the rail's own styles handle item wrapping + the
58
+ * description-hide behavior)
59
+ *
60
+ * ## Example
61
+ *
62
+ * ```typescript
63
+ * sections: MJLeftNavSection[] = [
64
+ * {
65
+ * items: [
66
+ * { id: 'users', icon: 'fa-solid fa-users', label: 'Users', description: 'Manage user accounts' },
67
+ * { id: 'roles', icon: 'fa-solid fa-user-shield', label: 'Roles', description: 'Define roles and assignments' },
68
+ * ]
69
+ * }
70
+ * ];
71
+ * activeId = 'users';
72
+ * ```
73
+ *
74
+ * ```html
75
+ * <mj-left-nav
76
+ * [Sections]="sections"
77
+ * [ActiveId]="activeId"
78
+ * (ItemClicked)="onItemClicked($event)">
79
+ * </mj-left-nav>
80
+ * ```
81
+ *
82
+ * Responsive collapse — the rail's own CSS handles narrow-viewport item
83
+ * wrapping. The consumer is responsible for switching the parent's
84
+ * flex-direction from row → column at the same breakpoint, e.g. via
85
+ * `<mj-page-body>` Direction="row" with a `@media` override in the shell's CSS.
86
+ */
87
+ export declare class MJLeftNavComponent {
88
+ /**
89
+ * The sections + items that make up the rail. Sections render top-to-bottom
90
+ * in array order; items inside each section render in array order.
91
+ */
92
+ Sections: MJLeftNavSection[];
93
+ /**
94
+ * The id of the currently active item. Items matching this id get the
95
+ * active styling and `aria-current="page"`.
96
+ */
97
+ ActiveId: string | null;
98
+ /**
99
+ * Width of the rail in pixels. Defaults to 240. Set to a smaller value
100
+ * (e.g., 200) for denser shells.
101
+ */
102
+ Width: number;
103
+ /**
104
+ * Ids of currently-expanded tree items. Items whose id appears here render
105
+ * with a down-chevron and their `children` are visible; items not in this
106
+ * list render with a right-chevron and their children stay collapsed.
107
+ * Consumer owns the state — toggle in response to `(ItemToggled)`.
108
+ *
109
+ * Only applies to items where `children !== undefined`; flat items ignore it.
110
+ */
111
+ ExpandedIds: string[];
112
+ /**
113
+ * Emitted when a non-disabled item is clicked.
114
+ */
115
+ ItemClicked: EventEmitter<MJLeftNavItem>;
116
+ /**
117
+ * Emitted when a tree item's chevron is clicked. Consumer should toggle
118
+ * the item's id in / out of `ExpandedIds`. Does NOT also fire `ItemClicked`.
119
+ */
120
+ ItemToggled: EventEmitter<MJLeftNavItem>;
121
+ OnItemClick(item: MJLeftNavItem): void;
122
+ OnItemToggle(event: Event, item: MJLeftNavItem): void;
123
+ IsExpanded(id: string): boolean;
124
+ /**
125
+ * Whether any top-level item in this section actually has children to expand.
126
+ * The template calls this once per section; the result is threaded through
127
+ * the recursive itemTpl so leaf nodes only reserve a chevron-column
128
+ * placeholder when a sibling will actually render a chevron.
129
+ *
130
+ * Top-level check is sufficient — if a deeply-nested item has children, its
131
+ * parent's `children.length > 0` already qualifies the section as expandable.
132
+ */
133
+ HasExpandableItems(items: MJLeftNavItem[]): boolean;
134
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJLeftNavComponent, never>;
135
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJLeftNavComponent, "mj-left-nav", never, { "Sections": { "alias": "Sections"; "required": false; }; "ActiveId": { "alias": "ActiveId"; "required": false; }; "Width": { "alias": "Width"; "required": false; }; "ExpandedIds": { "alias": "ExpandedIds"; "required": false; }; }, { "ItemClicked": "ItemClicked"; "ItemToggled": "ItemToggled"; }, never, ["[header]", "[footer]"], true, never>;
136
+ }
137
+ //# sourceMappingURL=left-nav.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"left-nav.component.d.ts","sourceRoot":"","sources":["../../../src/lib/left-nav/left-nav.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAiB,MAAM,eAAe,CAAC;;AAGvE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+EAA+E;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAqSa,kBAAkB;IAC7B;;;OAGG;IACM,QAAQ,EAAE,gBAAgB,EAAE,CAAM;IAE3C;;;OAGG;IACM,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExC;;;OAGG;IACM,KAAK,EAAE,MAAM,CAAO;IAE7B;;;;;;;OAOG;IACM,WAAW,EAAE,MAAM,EAAE,CAAM;IAEpC;;OAEG;IACO,WAAW,8BAAqC;IAE1D;;;OAGG;IACO,WAAW,8BAAqC;IAE1D,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAKtC,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAKrD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO;yCA/DxC,kBAAkB;2CAAlB,kBAAkB;CAqE9B"}