@memberjunction/ng-ui-components 5.38.0 → 5.40.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 (39) hide show
  1. package/dist/lib/applied-filters/applied-filters.component.d.ts +55 -0
  2. package/dist/lib/applied-filters/applied-filters.component.d.ts.map +1 -0
  3. package/dist/lib/applied-filters/applied-filters.component.js +128 -0
  4. package/dist/lib/applied-filters/applied-filters.component.js.map +1 -0
  5. package/dist/lib/filter-panel/filter-panel.component.d.ts +14 -0
  6. package/dist/lib/filter-panel/filter-panel.component.d.ts.map +1 -1
  7. package/dist/lib/filter-panel/filter-panel.component.js +30 -5
  8. package/dist/lib/filter-panel/filter-panel.component.js.map +1 -1
  9. package/dist/lib/filter-popover/filter-popover.component.d.ts +14 -2
  10. package/dist/lib/filter-popover/filter-popover.component.d.ts.map +1 -1
  11. package/dist/lib/filter-popover/filter-popover.component.js +153 -52
  12. package/dist/lib/filter-popover/filter-popover.component.js.map +1 -1
  13. package/dist/lib/left-nav/left-nav-content.component.d.ts.map +1 -1
  14. package/dist/lib/left-nav/left-nav-content.component.js +3 -3
  15. package/dist/lib/left-nav/left-nav-content.component.js.map +1 -1
  16. package/dist/lib/left-nav/left-nav.component.d.ts +36 -9
  17. package/dist/lib/left-nav/left-nav.component.d.ts.map +1 -1
  18. package/dist/lib/left-nav/left-nav.component.js +253 -117
  19. package/dist/lib/left-nav/left-nav.component.js.map +1 -1
  20. package/dist/lib/page-body/page-body.component.d.ts.map +1 -1
  21. package/dist/lib/page-body/page-body.component.js +3 -3
  22. package/dist/lib/page-body/page-body.component.js.map +1 -1
  23. package/dist/lib/page-header/page-header.component.js +2 -2
  24. package/dist/lib/page-header/page-header.scss +51 -4
  25. package/dist/lib/page-header-interior/page-header-interior.component.d.ts.map +1 -1
  26. package/dist/lib/page-header-interior/page-header-interior.component.js +3 -3
  27. package/dist/lib/page-header-interior/page-header-interior.component.js.map +1 -1
  28. package/dist/lib/refresh-button/refresh-button.component.d.ts.map +1 -1
  29. package/dist/lib/refresh-button/refresh-button.component.js +13 -10
  30. package/dist/lib/refresh-button/refresh-button.component.js.map +1 -1
  31. package/dist/lib/slide-panel/slide-panel.component.d.ts +64 -0
  32. package/dist/lib/slide-panel/slide-panel.component.d.ts.map +1 -0
  33. package/dist/lib/slide-panel/slide-panel.component.js +256 -0
  34. package/dist/lib/slide-panel/slide-panel.component.js.map +1 -0
  35. package/dist/public-api.d.ts +1 -0
  36. package/dist/public-api.d.ts.map +1 -1
  37. package/dist/public-api.js +1 -0
  38. package/dist/public-api.js.map +1 -1
  39. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
- import { Component, EventEmitter, Input, Output } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, Output, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
2
3
  import { OverlayModule } from '@angular/cdk/overlay';
3
4
  import * as i0 from "@angular/core";
4
- import * as i1 from "@angular/cdk/overlay";
5
+ import * as i1 from "@angular/common";
6
+ import * as i2 from "@angular/cdk/overlay";
5
7
  const _c0 = ["*"];
6
8
  function MJFilterPopoverComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
7
- i0.ɵɵelementStart(0, "span", 2);
9
+ i0.ɵɵelementStart(0, "span", 4);
8
10
  i0.ɵɵtext(1);
9
11
  i0.ɵɵelementEnd();
10
12
  } if (rf & 2) {
@@ -13,29 +15,77 @@ function MJFilterPopoverComponent_Conditional_5_Template(rf, ctx) { if (rf & 1)
13
15
  i0.ɵɵadvance();
14
16
  i0.ɵɵtextInterpolate1(" ", ctx_r1.ActiveCount, " ");
15
17
  } }
16
- function MJFilterPopoverComponent_ng_template_7_Conditional_4_Template(rf, ctx) { if (rf & 1) {
18
+ function MJFilterPopoverComponent_ng_template_7_Conditional_3_Template(rf, ctx) { if (rf & 1) {
17
19
  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.ɵɵelementStart(0, "button", 11);
21
+ i0.ɵɵlistener("click", function MJFilterPopoverComponent_ng_template_7_Conditional_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ClearAll()); });
20
22
  i0.ɵɵtext(1, " Clear all ");
21
23
  i0.ɵɵelementEnd();
22
24
  } }
23
25
  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.ɵɵelementStart(0, "div", 7)(1, "span", 8);
27
+ i0.ɵɵtext(2);
26
28
  i0.ɵɵelementEnd();
27
- i0.ɵɵconditionalCreate(4, MJFilterPopoverComponent_ng_template_7_Conditional_4_Template, 2, 0, "button", 8);
29
+ i0.ɵɵconditionalCreate(3, MJFilterPopoverComponent_ng_template_7_Conditional_3_Template, 2, 0, "button", 9);
30
+ i0.ɵɵelementEnd();
31
+ i0.ɵɵelementStart(4, "div", 10);
32
+ i0.ɵɵprojection(5);
28
33
  i0.ɵɵelementEnd();
29
- i0.ɵɵelementStart(5, "div", 9);
30
- i0.ɵɵprojection(6);
31
- i0.ɵɵelementEnd()();
32
34
  } if (rf & 2) {
33
35
  const ctx_r1 = i0.ɵɵnextContext();
34
- i0.ɵɵattribute("aria-label", ctx_r1.Label);
35
- i0.ɵɵadvance(3);
36
+ i0.ɵɵadvance(2);
36
37
  i0.ɵɵtextInterpolate(ctx_r1.Label);
37
38
  i0.ɵɵadvance();
38
- i0.ɵɵconditional(ctx_r1.ActiveCount > 0 && ctx_r1.ShowClearAll ? 4 : -1);
39
+ i0.ɵɵconditional(ctx_r1.ActiveCount > 0 && ctx_r1.ShowClearAll ? 3 : -1);
40
+ } }
41
+ function MJFilterPopoverComponent_Conditional_9_Conditional_0_ng_container_3_Template(rf, ctx) { if (rf & 1) {
42
+ i0.ɵɵelementContainer(0);
43
+ } }
44
+ function MJFilterPopoverComponent_Conditional_9_Conditional_0_Template(rf, ctx) { if (rf & 1) {
45
+ const _r4 = i0.ɵɵgetCurrentView();
46
+ i0.ɵɵelementStart(0, "div", 12);
47
+ i0.ɵɵlistener("click", function MJFilterPopoverComponent_Conditional_9_Conditional_0_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.Close()); });
48
+ i0.ɵɵelementEnd();
49
+ i0.ɵɵelementStart(1, "div", 13);
50
+ i0.ɵɵelement(2, "div", 14);
51
+ i0.ɵɵtemplate(3, MJFilterPopoverComponent_Conditional_9_Conditional_0_ng_container_3_Template, 1, 0, "ng-container", 15);
52
+ i0.ɵɵelementEnd();
53
+ } if (rf & 2) {
54
+ const ctx_r1 = i0.ɵɵnextContext(2);
55
+ const panelBody_r5 = i0.ɵɵreference(8);
56
+ i0.ɵɵadvance();
57
+ i0.ɵɵattribute("aria-label", ctx_r1.Label);
58
+ i0.ɵɵadvance(2);
59
+ i0.ɵɵproperty("ngTemplateOutlet", panelBody_r5);
60
+ } }
61
+ function MJFilterPopoverComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
62
+ i0.ɵɵconditionalCreate(0, MJFilterPopoverComponent_Conditional_9_Conditional_0_Template, 4, 2);
63
+ } if (rf & 2) {
64
+ const ctx_r1 = i0.ɵɵnextContext();
65
+ i0.ɵɵconditional(ctx_r1.IsOpen ? 0 : -1);
66
+ } }
67
+ function MJFilterPopoverComponent_Conditional_10_ng_template_0_ng_container_1_Template(rf, ctx) { if (rf & 1) {
68
+ i0.ɵɵelementContainer(0);
69
+ } }
70
+ function MJFilterPopoverComponent_Conditional_10_ng_template_0_Template(rf, ctx) { if (rf & 1) {
71
+ i0.ɵɵelementStart(0, "div", 17);
72
+ i0.ɵɵtemplate(1, MJFilterPopoverComponent_Conditional_10_ng_template_0_ng_container_1_Template, 1, 0, "ng-container", 15);
73
+ i0.ɵɵelementEnd();
74
+ } if (rf & 2) {
75
+ const ctx_r1 = i0.ɵɵnextContext(2);
76
+ const panelBody_r5 = i0.ɵɵreference(8);
77
+ i0.ɵɵattribute("aria-label", ctx_r1.Label);
78
+ i0.ɵɵadvance();
79
+ i0.ɵɵproperty("ngTemplateOutlet", panelBody_r5);
80
+ } }
81
+ function MJFilterPopoverComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
82
+ const _r6 = i0.ɵɵgetCurrentView();
83
+ i0.ɵɵtemplate(0, MJFilterPopoverComponent_Conditional_10_ng_template_0_Template, 2, 2, "ng-template", 16);
84
+ i0.ɵɵlistener("backdropClick", function MJFilterPopoverComponent_Conditional_10_Template_ng_template_backdropClick_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Close()); })("detach", function MJFilterPopoverComponent_Conditional_10_Template_ng_template_detach_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Close()); });
85
+ } if (rf & 2) {
86
+ const ctx_r1 = i0.ɵɵnextContext();
87
+ const overlayOrigin_r7 = i0.ɵɵreference(1);
88
+ i0.ɵɵproperty("cdkConnectedOverlayOrigin", overlayOrigin_r7)("cdkConnectedOverlayOpen", ctx_r1.IsOpen)("cdkConnectedOverlayPositions", ctx_r1.Positions)("cdkConnectedOverlayHasBackdrop", true);
39
89
  } }
40
90
  /**
41
91
  * mj-filter-popover — Compact "Filters" trigger button that opens a popover
@@ -61,6 +111,38 @@ export class MJFilterPopoverComponent {
61
111
  ActiveCount = 0;
62
112
  ShowClearAll = false;
63
113
  IsOpen = false;
114
+ /**
115
+ * True when the viewport is narrow enough that the popover should dock as a
116
+ * bottom sheet instead of an anchored overlay. Driven by matchMedia so it
117
+ * tracks live resize without Angular change-detection plumbing.
118
+ */
119
+ IsMobile = false;
120
+ zone = inject(NgZone);
121
+ cdr = inject(ChangeDetectorRef);
122
+ mediaQuery = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
123
+ ? window.matchMedia('(max-width: 700px)')
124
+ : null;
125
+ // matchMedia 'change' fires OUTSIDE Angular's zone, so the flip must be
126
+ // re-entered into the zone — otherwise the IsMobile change lands mid-CD and
127
+ // the @if(IsMobile) branch swap throws ExpressionChangedAfterItHasBeenChecked
128
+ // when crossing the breakpoint. Also close across the switch so a sheet/overlay
129
+ // opened in one mode doesn't linger in the other.
130
+ onMediaChange = (e) => {
131
+ this.zone.run(() => {
132
+ this.IsMobile = e.matches;
133
+ this.IsOpen = false;
134
+ this.cdr.markForCheck();
135
+ });
136
+ };
137
+ constructor() {
138
+ if (this.mediaQuery) {
139
+ this.IsMobile = this.mediaQuery.matches;
140
+ this.mediaQuery.addEventListener('change', this.onMediaChange);
141
+ }
142
+ }
143
+ ngOnDestroy() {
144
+ this.mediaQuery?.removeEventListener('change', this.onMediaChange);
145
+ }
64
146
  // Note: NO offsetY here — CDK applies offsetY as `transform: translateY(...)` on the
65
147
  // overlay pane, which creates a containing block for `position: fixed` descendants.
66
148
  // Tree dropdowns (and any other component using position: fixed) would then mis-position.
@@ -79,22 +161,21 @@ export class MJFilterPopoverComponent {
79
161
  this.ClearAllRequested.emit();
80
162
  }
81
163
  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) {
164
+ 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: 11, vars: 11, consts: [["overlayOrigin", "cdkOverlayOrigin"], ["panelBody", ""], ["type", "button", "cdkOverlayOrigin", "", 1, "mj-filter-popover-trigger", 3, "click"], [1, "mj-filter-popover-label"], [1, "mj-filter-popover-badge"], [1, "fa-solid", "fa-chevron-down", "mj-filter-popover-chevron"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "mj-filter-popover-backdrop", 3, "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayHasBackdrop"], [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"], ["aria-hidden", "true", 1, "mj-filter-popover-scrim", 3, "click"], ["role", "dialog", 1, "mj-filter-popover-sheet"], ["aria-hidden", "true", 1, "mj-filter-popover-grab"], [4, "ngTemplateOutlet"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "mj-filter-popover-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayHasBackdrop"], ["role", "dialog", 1, "mj-filter-popover-panel"]], template: function MJFilterPopoverComponent_Template(rf, ctx) { if (rf & 1) {
83
165
  const _r1 = i0.ɵɵgetCurrentView();
84
166
  i0.ɵɵprojectionDef();
85
- i0.ɵɵelementStart(0, "button", 1, 0);
167
+ i0.ɵɵelementStart(0, "button", 2, 0);
86
168
  i0.ɵɵlistener("click", function MJFilterPopoverComponent_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Toggle()); });
87
169
  i0.ɵɵelement(2, "i");
88
- i0.ɵɵelementStart(3, "span");
170
+ i0.ɵɵelementStart(3, "span", 3);
89
171
  i0.ɵɵtext(4);
90
172
  i0.ɵɵelementEnd();
91
- i0.ɵɵconditionalCreate(5, MJFilterPopoverComponent_Conditional_5_Template, 2, 2, "span", 2);
92
- i0.ɵɵelement(6, "i", 3);
173
+ i0.ɵɵconditionalCreate(5, MJFilterPopoverComponent_Conditional_5_Template, 2, 2, "span", 4);
174
+ i0.ɵɵelement(6, "i", 5);
93
175
  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()); });
176
+ i0.ɵɵtemplate(7, MJFilterPopoverComponent_ng_template_7_Template, 6, 2, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
177
+ i0.ɵɵconditionalCreate(9, MJFilterPopoverComponent_Conditional_9_Template, 1, 1)(10, MJFilterPopoverComponent_Conditional_10_Template, 1, 4, null, 6);
96
178
  } if (rf & 2) {
97
- const overlayOrigin_r4 = i0.ɵɵreference(1);
98
179
  i0.ɵɵclassProp("mj-filter-popover-trigger--active", ctx.IsOpen || ctx.ActiveCount > 0);
99
180
  i0.ɵɵattribute("aria-expanded", ctx.IsOpen)("aria-haspopup", true);
100
181
  i0.ɵɵadvance(2);
@@ -105,13 +186,13 @@ export class MJFilterPopoverComponent {
105
186
  i0.ɵɵconditional(ctx.ActiveCount > 0 ? 5 : -1);
106
187
  i0.ɵɵadvance();
107
188
  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 }"] });
189
+ i0.ɵɵadvance(3);
190
+ i0.ɵɵconditional(ctx.IsMobile ? 9 : 10);
191
+ } }, dependencies: [CommonModule, i1.NgTemplateOutlet, OverlayModule, i2.CdkConnectedOverlay, i2.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 }\n\n \n\n\n\n\n .mj-filter-popover-scrim[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 9998;\n animation: _ngcontent-%COMP%_mj-fp-fade 0.2s ease;\n }\n .mj-filter-popover-sheet[_ngcontent-%COMP%] {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n max-height: 85vh;\n background: var(--mj-bg-surface);\n border-top-left-radius: 16px;\n border-top-right-radius: 16px;\n box-shadow: var(--mj-shadow-lg);\n animation: _ngcontent-%COMP%_mj-fp-slide-up 0.24s ease;\n }\n .mj-filter-popover-grab[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--mj-border-strong);\n margin: 10px auto 2px;\n }\n .mj-filter-popover-sheet[_ngcontent-%COMP%] .mj-filter-popover-header[_ngcontent-%COMP%] {\n flex-shrink: 0;\n }\n .mj-filter-popover-sheet[_ngcontent-%COMP%] .mj-filter-popover-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding-bottom: var(--mj-space-6);\n }\n @keyframes _ngcontent-%COMP%_mj-fp-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes _ngcontent-%COMP%_mj-fp-slide-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n }\n @media (prefers-reduced-motion: reduce) {\n .mj-filter-popover-scrim[_ngcontent-%COMP%], \n .mj-filter-popover-sheet[_ngcontent-%COMP%] {\n animation: none;\n }\n }\n\n \n\n\n\n @media (max-width: 700px) {\n .mj-filter-popover-label[_ngcontent-%COMP%], \n .mj-filter-popover-chevron[_ngcontent-%COMP%] {\n display: none;\n }\n .mj-filter-popover-trigger[_ngcontent-%COMP%] {\n padding: 6px 10px;\n gap: 4px;\n }\n }"] });
111
192
  }
112
193
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJFilterPopoverComponent, [{
113
194
  type: Component,
114
- args: [{ selector: 'mj-filter-popover', standalone: true, imports: [OverlayModule], template: `
195
+ args: [{ selector: 'mj-filter-popover', standalone: true, imports: [CommonModule, OverlayModule], template: `
115
196
  <button
116
197
  type="button"
117
198
  class="mj-filter-popover-trigger"
@@ -122,7 +203,7 @@ export class MJFilterPopoverComponent {
122
203
  [attr.aria-haspopup]="true"
123
204
  (click)="Toggle()">
124
205
  <i [class]="Icon"></i>
125
- <span>{{ Label }}</span>
206
+ <span class="mj-filter-popover-label">{{ Label }}</span>
126
207
  @if (ActiveCount > 0) {
127
208
  <span class="mj-filter-popover-badge" [attr.aria-label]="ActiveCount + ' active filters'">
128
209
  {{ ActiveCount }}
@@ -131,31 +212,51 @@ export class MJFilterPopoverComponent {
131
212
  <i class="fa-solid fa-chevron-down mj-filter-popover-chevron" [class.mj-filter-popover-chevron--open]="IsOpen"></i>
132
213
  </button>
133
214
 
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>
215
+ <!-- The panel body (header + projected filter UI) is declared ONCE here and
216
+ stamped into whichever wrapper is active (anchored overlay on desktop,
217
+ docked sheet on mobile). A single <ng-content> avoids the duplicate-slot
218
+ bug where projected content lands in only one of two branches. -->
219
+ <ng-template #panelBody>
220
+ <div class="mj-filter-popover-header">
221
+ <span class="mj-filter-popover-title">{{ Label }}</span>
222
+ @if (ActiveCount > 0 && ShowClearAll) {
223
+ <button type="button" class="mj-filter-popover-clear" (click)="ClearAll()">
224
+ Clear all
225
+ </button>
226
+ }
227
+ </div>
228
+ <div class="mj-filter-popover-content">
229
+ <ng-content></ng-content>
155
230
  </div>
156
231
  </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: [{
232
+
233
+ @if (IsMobile) {
234
+ <!-- Mobile: the popover docks as a bottom sheet — mirrors how mj-left-nav
235
+ becomes a drawer at this width. -->
236
+ @if (IsOpen) {
237
+ <div class="mj-filter-popover-scrim" (click)="Close()" aria-hidden="true"></div>
238
+ <div class="mj-filter-popover-sheet" role="dialog" [attr.aria-label]="Label">
239
+ <div class="mj-filter-popover-grab" aria-hidden="true"></div>
240
+ <ng-container *ngTemplateOutlet="panelBody"></ng-container>
241
+ </div>
242
+ }
243
+ } @else {
244
+ <ng-template
245
+ cdkConnectedOverlay
246
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
247
+ [cdkConnectedOverlayOpen]="IsOpen"
248
+ [cdkConnectedOverlayPositions]="Positions"
249
+ [cdkConnectedOverlayHasBackdrop]="true"
250
+ cdkConnectedOverlayBackdropClass="mj-filter-popover-backdrop"
251
+ (backdropClick)="Close()"
252
+ (detach)="Close()">
253
+ <div class="mj-filter-popover-panel" role="dialog" [attr.aria-label]="Label">
254
+ <ng-container *ngTemplateOutlet="panelBody"></ng-container>
255
+ </div>
256
+ </ng-template>
257
+ }
258
+ `, 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\n /* \u2500\u2500 Mobile bottom sheet (\u2264700px) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n The trigger button stays inline; tapping it docks the panel body as a\n sheet at the bottom of the viewport over a scrim \u2014 same body, different\n wrapper. Mirrors the mj-left-nav drawer treatment. */\n .mj-filter-popover-scrim {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 9998;\n animation: mj-fp-fade 0.2s ease;\n }\n .mj-filter-popover-sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n max-height: 85vh;\n background: var(--mj-bg-surface);\n border-top-left-radius: 16px;\n border-top-right-radius: 16px;\n box-shadow: var(--mj-shadow-lg);\n animation: mj-fp-slide-up 0.24s ease;\n }\n .mj-filter-popover-grab {\n flex-shrink: 0;\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--mj-border-strong);\n margin: 10px auto 2px;\n }\n .mj-filter-popover-sheet .mj-filter-popover-header {\n flex-shrink: 0;\n }\n .mj-filter-popover-sheet .mj-filter-popover-content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding-bottom: var(--mj-space-6);\n }\n @keyframes mj-fp-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes mj-fp-slide-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n }\n @media (prefers-reduced-motion: reduce) {\n .mj-filter-popover-scrim,\n .mj-filter-popover-sheet {\n animation: none;\n }\n }\n\n /* On mobile the trigger collapses to icon-only (the count badge still\n conveys active state) so the control bar \u2014 search \u00B7 Filter \u00B7 view \u2014\n stays on one line. */\n @media (max-width: 700px) {\n .mj-filter-popover-label,\n .mj-filter-popover-chevron {\n display: none;\n }\n .mj-filter-popover-trigger {\n padding: 6px 10px;\n gap: 4px;\n }\n }\n "] }]
259
+ }], () => [], { Label: [{
159
260
  type: Input
160
261
  }], Icon: [{
161
262
  type: Input
@@ -166,5 +267,5 @@ export class MJFilterPopoverComponent {
166
267
  }], ClearAllRequested: [{
167
268
  type: Output
168
269
  }] }); })();
169
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJFilterPopoverComponent, { className: "MJFilterPopoverComponent", filePath: "lib/filter-popover/filter-popover.component.ts", lineNumber: 189 }); })();
270
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJFilterPopoverComponent, { className: "MJFilterPopoverComponent", filePath: "lib/filter-popover/filter-popover.component.ts", lineNumber: 283 }); })();
170
271
  //# sourceMappingURL=filter-popover.component.js.map
@@ -1 +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"]}
1
+ {"version":3,"file":"filter-popover.component.js","sourceRoot":"","sources":["../../../src/lib/filter-popover/filter-popover.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAa,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;;;;;;IAqChE,+BAA0F;IACxF,YACF;IAAA,iBAAO;;;;IADL,cACF;IADE,mDACF;;;;IAaE,kCAA2E;IAArB,2MAAS,iBAAU,KAAC;IACxE,2BACF;IAAA,iBAAS;;;IAJX,AADF,8BAAsC,cACE;IAAA,YAAW;IAAA,iBAAO;IACxD,2GAAuC;IAKzC,iBAAM;IACN,+BAAuC;IACrC,kBAAyB;IAC3B,iBAAM;;;IATkC,eAAW;IAAX,kCAAW;IACjD,cAIC;IAJD,wEAIC;;;IAcC,wBAA2D;;;;IAH7D,+BAA0E;IAArC,wMAAS,cAAO,KAAC;IAAoB,iBAAM;IAChF,+BAA6E;IAC3E,0BAA6D;IAC7D,wHAA4C;IAC9C,iBAAM;;;;IAH6C,cAAyB;;IAE3D,eAA2B;IAA3B,+CAA2B;;;IAJ9C,8FAAc;;;IAAd,wCAMC;;;IAYG,wBAA2D;;;IAD7D,+BAA6E;IAC3E,yHAA4C;IAC9C,iBAAM;;;;;IADW,cAA2B;IAA3B,+CAA2B;;;;IAV9C,yGAQqB;IAAnB,AADA,kNAAiB,cAAO,KAAC,uLACf,cAAO,KAAC;;;;IAHlB,AADA,AADA,AADA,4DAA2C,0CACT,kDACQ,wCACH;;AA5E/C;;;;;;;;;;;;;;;;;GAiBG;AAqQH,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;;;;OAIG;IACI,QAAQ,GAAY,KAAK,CAAC;IAEhB,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEhC,UAAU,GACzB,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU;QACtE,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC;QACzC,CAAC,CAAC,IAAI,CAAC;IAEX,wEAAwE;IACxE,4EAA4E;IAC5E,8EAA8E;IAC9E,gFAAgF;IAChF,kDAAkD;IACjC,aAAa,GAAG,CAAC,CAAsB,EAAQ,EAAE;QAChE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;YACjB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF;QACE,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,WAAW;QACT,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,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;kHAlEU,wBAAwB;6DAAxB,wBAAwB;;;YA/PjC,oCAQqB;YAAnB,2IAAS,YAAQ,KAAC;YAClB,oBAAsB;YACtB,+BAAsC;YAAA,YAAW;YAAA,iBAAO;YACxD,2FAAuB;YAKvB,uBAAmH;YACrH,iBAAS;YAMT,0HAAwB;YAwBtB,AAVF,gFAAgB,qEAUP;;YA5CP,sFAAqE;;YAMlE,eAAc;YAAd,uBAAc;YACqB,eAAW;YAAX,+BAAW;YACjD,cAIC;YAJD,8CAIC;YAC6D,cAAgD;YAAhD,6DAAgD;YAqBhH,eAwBC;YAxBD,uCAwBC;4BA/DO,YAAY,uBAAE,aAAa;;iFAiQ1B,wBAAwB;cApQpC,SAAS;2BACE,mBAAmB,cACjB,IAAI,WACP,CAAC,YAAY,EAAE,aAAa,CAAC,YAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DT;;kBAkMA,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBA0DL,MAAM;;kFA9DI,wBAAwB","sourcesContent":["import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, Output, inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\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: [CommonModule, 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 class=\"mj-filter-popover-label\">{{ 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 <!-- The panel body (header + projected filter UI) is declared ONCE here and\n stamped into whichever wrapper is active (anchored overlay on desktop,\n docked sheet on mobile). A single <ng-content> avoids the duplicate-slot\n bug where projected content lands in only one of two branches. -->\n <ng-template #panelBody>\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 </ng-template>\n\n @if (IsMobile) {\n <!-- Mobile: the popover docks as a bottom sheet — mirrors how mj-left-nav\n becomes a drawer at this width. -->\n @if (IsOpen) {\n <div class=\"mj-filter-popover-scrim\" (click)=\"Close()\" aria-hidden=\"true\"></div>\n <div class=\"mj-filter-popover-sheet\" role=\"dialog\" [attr.aria-label]=\"Label\">\n <div class=\"mj-filter-popover-grab\" aria-hidden=\"true\"></div>\n <ng-container *ngTemplateOutlet=\"panelBody\"></ng-container>\n </div>\n }\n } @else {\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 <ng-container *ngTemplateOutlet=\"panelBody\"></ng-container>\n </div>\n </ng-template>\n }\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 /* ── Mobile bottom sheet (≤700px) ─────────────────────────────────────\n The trigger button stays inline; tapping it docks the panel body as a\n sheet at the bottom of the viewport over a scrim — same body, different\n wrapper. Mirrors the mj-left-nav drawer treatment. */\n .mj-filter-popover-scrim {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 9998;\n animation: mj-fp-fade 0.2s ease;\n }\n .mj-filter-popover-sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n max-height: 85vh;\n background: var(--mj-bg-surface);\n border-top-left-radius: 16px;\n border-top-right-radius: 16px;\n box-shadow: var(--mj-shadow-lg);\n animation: mj-fp-slide-up 0.24s ease;\n }\n .mj-filter-popover-grab {\n flex-shrink: 0;\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--mj-border-strong);\n margin: 10px auto 2px;\n }\n .mj-filter-popover-sheet .mj-filter-popover-header {\n flex-shrink: 0;\n }\n .mj-filter-popover-sheet .mj-filter-popover-content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding-bottom: var(--mj-space-6);\n }\n @keyframes mj-fp-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes mj-fp-slide-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n }\n @media (prefers-reduced-motion: reduce) {\n .mj-filter-popover-scrim,\n .mj-filter-popover-sheet {\n animation: none;\n }\n }\n\n /* On mobile the trigger collapses to icon-only (the count badge still\n conveys active state) so the control bar — search · Filter · view —\n stays on one line. */\n @media (max-width: 700px) {\n .mj-filter-popover-label,\n .mj-filter-popover-chevron {\n display: none;\n }\n .mj-filter-popover-trigger {\n padding: 6px 10px;\n gap: 4px;\n }\n }\n `]\n})\nexport class MJFilterPopoverComponent implements OnDestroy {\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 /**\n * True when the viewport is narrow enough that the popover should dock as a\n * bottom sheet instead of an anchored overlay. Driven by matchMedia so it\n * tracks live resize without Angular change-detection plumbing.\n */\n public IsMobile: boolean = false;\n\n private readonly zone = inject(NgZone);\n private readonly cdr = inject(ChangeDetectorRef);\n\n private readonly mediaQuery: MediaQueryList | null =\n typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(max-width: 700px)')\n : null;\n\n // matchMedia 'change' fires OUTSIDE Angular's zone, so the flip must be\n // re-entered into the zone — otherwise the IsMobile change lands mid-CD and\n // the @if(IsMobile) branch swap throws ExpressionChangedAfterItHasBeenChecked\n // when crossing the breakpoint. Also close across the switch so a sheet/overlay\n // opened in one mode doesn't linger in the other.\n private readonly onMediaChange = (e: MediaQueryListEvent): void => {\n this.zone.run(() => {\n this.IsMobile = e.matches;\n this.IsOpen = false;\n this.cdr.markForCheck();\n });\n };\n\n constructor() {\n if (this.mediaQuery) {\n this.IsMobile = this.mediaQuery.matches;\n this.mediaQuery.addEventListener('change', this.onMediaChange);\n }\n }\n\n ngOnDestroy(): void {\n this.mediaQuery?.removeEventListener('change', this.onMediaChange);\n }\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"]}
@@ -1 +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"}
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,qBAuFa,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"}
@@ -86,7 +86,7 @@ export class MJLeftNavContentComponent {
86
86
  i0.ɵɵprojection(2);
87
87
  } if (rf & 2) {
88
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 }"] });
89
+ } }, dependencies: [CommonModule], styles: ["[_nghost-%COMP%] {\n flex: 1;\n min-width: 0;\n \n\n\n\n\n\n min-height: 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
90
  }
91
91
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJLeftNavContentComponent, [{
92
92
  type: Component,
@@ -103,7 +103,7 @@ export class MJLeftNavContentComponent {
103
103
  </div>
104
104
  }
105
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 "] }]
106
+ `, styles: ["\n :host {\n flex: 1;\n min-width: 0;\n /* min-height: 0 is essential when the parent <mj-page-body> reflows from\n row \u2192 column on mobile: without it this pane can't shrink below its\n content height, so the inner scroller (mj-page-body-interior) never\n gets a bounded height and the page can't scroll. Harmless in the row\n (desktop) case. */\n min-height: 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
107
  }], null, { Loading: [{
108
108
  type: Input
109
109
  }], Error: [{
@@ -117,5 +117,5 @@ export class MJLeftNavContentComponent {
117
117
  type: HostBinding,
118
118
  args: ['attr.aria-busy']
119
119
  }] }); })();
120
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJLeftNavContentComponent, { className: "MJLeftNavContentComponent", filePath: "lib/left-nav/left-nav-content.component.ts", lineNumber: 114 }); })();
120
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJLeftNavContentComponent, { className: "MJLeftNavContentComponent", filePath: "lib/left-nav/left-nav-content.component.ts", lineNumber: 120 }); })();
121
121
  //# sourceMappingURL=left-nav-content.component.js.map
@@ -1 +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"]}
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;AAwFH,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;;;YA7EhC,AALF,2FAAa,qEAKS;YAMtB,kBAAyB;;YAXzB,sDAUC;4BAZO,YAAY;;iFAoFX,yBAAyB;cAvFrC,SAAS;2BACE,qBAAqB,cACnB,IAAI,WACP,CAAC,YAAY,CAAC,YACb;;;;;;;;;;;;;GAaT;;kBA2EA,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 /* min-height: 0 is essential when the parent <mj-page-body> reflows from\n row → column on mobile: without it this pane can't shrink below its\n content height, so the inner scroller (mj-page-body-interior) never\n gets a bounded height and the page can't scroll. Harmless in the row\n (desktop) case. */\n min-height: 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"]}
@@ -53,9 +53,10 @@ export interface MJLeftNavSection {
53
53
  * - Items with a count badge (APIKeys pattern)
54
54
  * - Sections with uppercase headers (Communication / Credentials pattern)
55
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)
56
+ * - Responsive mobile drawer (≤700px): the rail hides off-canvas and a compact
57
+ * section switcher takes its place; tapping it slides the full rail in over a
58
+ * scrim. The rail keeps its full content (trees, slots, descriptions, badges)
59
+ * inside the drawer — no flattening. Set `[MobileTitle]` for the drawer label.
59
60
  *
60
61
  * ## Example
61
62
  *
@@ -79,10 +80,11 @@ export interface MJLeftNavSection {
79
80
  * </mj-left-nav>
80
81
  * ```
81
82
  *
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.
83
+ * Responsive — at ≤700px the rail becomes an off-canvas drawer (see the feature
84
+ * list above). The consumer should still switch the parent's flex-direction from
85
+ * row → column at the same breakpoint (e.g. `<mj-page-body>` Direction="row" with
86
+ * a `@media` override) so the in-flow section switcher stacks above the content
87
+ * pane rather than competing with it for horizontal space.
86
88
  */
87
89
  export declare class MJLeftNavComponent {
88
90
  /**
@@ -97,9 +99,16 @@ export declare class MJLeftNavComponent {
97
99
  ActiveId: string | null;
98
100
  /**
99
101
  * Width of the rail in pixels. Defaults to 240. Set to a smaller value
100
- * (e.g., 200) for denser shells.
102
+ * (e.g., 200) for denser shells. Only applies to the desktop rail — on mobile
103
+ * the rail becomes a fixed-width off-canvas drawer.
101
104
  */
102
105
  Width: number;
106
+ /**
107
+ * Label shown on the mobile drawer header and used as the switcher fallback
108
+ * label when no item is active. Defaults to `'Menu'`. Set to something
109
+ * domain-specific (e.g. `'Sections'`, `'Test Suites'`) for clearer context.
110
+ */
111
+ MobileTitle: string;
103
112
  /**
104
113
  * Ids of currently-expanded tree items. Items whose id appears here render
105
114
  * with a down-chevron and their `children` are visible; items not in this
@@ -118,8 +127,26 @@ export declare class MJLeftNavComponent {
118
127
  * the item's id in / out of `ExpandedIds`. Does NOT also fire `ItemClicked`.
119
128
  */
120
129
  ItemToggled: EventEmitter<MJLeftNavItem>;
130
+ /**
131
+ * Whether the mobile off-canvas drawer is open. Component-owned, transient UI
132
+ * state — no effect above the 700px breakpoint where the rail is always inline.
133
+ */
134
+ MobileNavOpen: boolean;
135
+ /** Reflects {@link MobileNavOpen} onto the host so CSS can drive the drawer. */
136
+ get isMobileNavOpen(): boolean;
137
+ OpenMobileNav(): void;
138
+ CloseMobileNav(): void;
139
+ /** Escape closes the mobile drawer. No-op when it's already closed. */
140
+ onEscape(): void;
121
141
  OnItemClick(item: MJLeftNavItem): void;
122
142
  OnItemToggle(event: Event, item: MJLeftNavItem): void;
143
+ /**
144
+ * The item matching {@link ActiveId} (searched recursively through trees),
145
+ * used to label the mobile section switcher with the current section's icon
146
+ * and label. Returns `undefined` when nothing is active.
147
+ */
148
+ get ActiveTriggerItem(): MJLeftNavItem | undefined;
149
+ private findItemById;
123
150
  IsExpanded(id: string): boolean;
124
151
  /**
125
152
  * Whether any top-level item in this section actually has children to expand.
@@ -132,6 +159,6 @@ export declare class MJLeftNavComponent {
132
159
  */
133
160
  HasExpandableItems(items: MJLeftNavItem[]): boolean;
134
161
  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>;
162
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJLeftNavComponent, "mj-left-nav", never, { "Sections": { "alias": "Sections"; "required": false; }; "ActiveId": { "alias": "ActiveId"; "required": false; }; "Width": { "alias": "Width"; "required": false; }; "MobileTitle": { "alias": "MobileTitle"; "required": false; }; "ExpandedIds": { "alias": "ExpandedIds"; "required": false; }; }, { "ItemClicked": "ItemClicked"; "ItemToggled": "ItemToggled"; }, never, ["[header]", "[footer]"], true, never>;
136
163
  }
137
164
  //# sourceMappingURL=left-nav.component.d.ts.map
@@ -1 +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"}
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,EAA4C,MAAM,eAAe,CAAC;;AAGlG;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBA8ba,kBAAkB;IAC7B;;;OAGG;IACM,QAAQ,EAAE,gBAAgB,EAAE,CAAM;IAE3C;;;OAGG;IACM,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExC;;;;OAIG;IACM,KAAK,EAAE,MAAM,CAAO;IAE7B;;;;OAIG;IACM,WAAW,EAAE,MAAM,CAAU;IAEtC;;;;;;;OAOG;IACM,WAAW,EAAE,MAAM,EAAE,CAAM;IAEpC;;OAEG;IACO,WAAW,8BAAqC;IAE1D;;;OAGG;IACO,WAAW,8BAAqC;IAE1D;;;OAGG;IACI,aAAa,UAAS;IAE7B,gFAAgF;IAChF,IACI,eAAe,IAAI,OAAO,CAE7B;IAED,aAAa,IAAI,IAAI;IAIrB,cAAc,IAAI,IAAI;IAItB,uEAAuE;IAEvE,QAAQ,IAAI,IAAI;IAMhB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAOtC,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAMrD;;;;OAIG;IACH,IAAI,iBAAiB,IAAI,aAAa,GAAG,SAAS,CAGjD;IAED,OAAO,CAAC,YAAY;IAWpB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO;yCA3HxC,kBAAkB;2CAAlB,kBAAkB;CAiI9B"}