@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,399 @@
1
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from "@angular/core";
4
+ import * as i1 from "@angular/common";
5
+ const _c0 = [[["", "header", ""]], [["", "footer", ""]]];
6
+ const _c1 = ["[header]", "[footer]"];
7
+ const _c2 = (a0, a1) => ({ item: a0, depth: 0, hasExpandable: a1 });
8
+ const _c3 = a0 => ({ item: a0 });
9
+ const _c4 = (a0, a1, a2) => ({ item: a0, depth: a1, hasExpandable: a2 });
10
+ const _forTrack0 = ($index, $item) => $item.id;
11
+ function MJLeftNavComponent_For_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
12
+ i0.ɵɵelementStart(0, "div", 3);
13
+ i0.ɵɵtext(1);
14
+ i0.ɵɵelementEnd();
15
+ } if (rf & 2) {
16
+ const section_r1 = i0.ɵɵnextContext().$implicit;
17
+ i0.ɵɵadvance();
18
+ i0.ɵɵtextInterpolate(section_r1.label);
19
+ } }
20
+ function MJLeftNavComponent_For_3_For_2_ng_container_0_Template(rf, ctx) { if (rf & 1) {
21
+ i0.ɵɵelementContainer(0);
22
+ } }
23
+ function MJLeftNavComponent_For_3_For_2_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵtemplate(0, MJLeftNavComponent_For_3_For_2_ng_container_0_Template, 1, 0, "ng-container", 4);
25
+ } if (rf & 2) {
26
+ const item_r2 = ctx.$implicit;
27
+ const section_r1 = i0.ɵɵnextContext().$implicit;
28
+ const ctx_r2 = i0.ɵɵnextContext();
29
+ const itemTpl_r4 = i0.ɵɵreference(6);
30
+ i0.ɵɵproperty("ngTemplateOutlet", itemTpl_r4)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c2, item_r2, ctx_r2.HasExpandableItems(section_r1.items)));
31
+ } }
32
+ function MJLeftNavComponent_For_3_Template(rf, ctx) { if (rf & 1) {
33
+ i0.ɵɵconditionalCreate(0, MJLeftNavComponent_For_3_Conditional_0_Template, 2, 1, "div", 3);
34
+ i0.ɵɵrepeaterCreate(1, MJLeftNavComponent_For_3_For_2_Template, 1, 5, "ng-container", null, _forTrack0);
35
+ } if (rf & 2) {
36
+ const section_r1 = ctx.$implicit;
37
+ i0.ɵɵconditional(section_r1.label ? 0 : -1);
38
+ i0.ɵɵadvance();
39
+ i0.ɵɵrepeater(section_r1.items);
40
+ } }
41
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
42
+ const _r5 = i0.ɵɵgetCurrentView();
43
+ i0.ɵɵelementStart(0, "button", 8);
44
+ i0.ɵɵlistener("click", function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_1_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r5); const item_r6 = i0.ɵɵnextContext(2).item; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnItemToggle($event, item_r6)); });
45
+ i0.ɵɵelement(1, "i", 9);
46
+ i0.ɵɵelementEnd();
47
+ } if (rf & 2) {
48
+ const item_r6 = i0.ɵɵnextContext(2).item;
49
+ const ctx_r2 = i0.ɵɵnextContext();
50
+ i0.ɵɵattribute("aria-label", ctx_r2.IsExpanded(item_r6.id) ? "Collapse" : "Expand")("aria-expanded", ctx_r2.IsExpanded(item_r6.id));
51
+ i0.ɵɵadvance();
52
+ i0.ɵɵclassProp("fa-chevron-down", ctx_r2.IsExpanded(item_r6.id))("fa-chevron-right", !ctx_r2.IsExpanded(item_r6.id));
53
+ } }
54
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
55
+ i0.ɵɵelement(0, "span", 7);
56
+ } }
57
+ function MJLeftNavComponent_ng_template_5_Conditional_0_ng_container_3_Template(rf, ctx) { if (rf & 1) {
58
+ i0.ɵɵelementContainer(0);
59
+ } }
60
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_For_1_ng_container_0_Template(rf, ctx) { if (rf & 1) {
61
+ i0.ɵɵelementContainer(0);
62
+ } }
63
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_For_1_Template(rf, ctx) { if (rf & 1) {
64
+ i0.ɵɵtemplate(0, MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_For_1_ng_container_0_Template, 1, 0, "ng-container", 4);
65
+ } if (rf & 2) {
66
+ const child_r7 = ctx.$implicit;
67
+ const ctx_r7 = i0.ɵɵnextContext(3);
68
+ const depth_r9 = ctx_r7.depth;
69
+ const hasExpandable_r10 = ctx_r7.hasExpandable;
70
+ i0.ɵɵnextContext();
71
+ const itemTpl_r4 = i0.ɵɵreference(6);
72
+ i0.ɵɵproperty("ngTemplateOutlet", itemTpl_r4)("ngTemplateOutletContext", i0.ɵɵpureFunction3(2, _c4, child_r7, depth_r9 + 1, hasExpandable_r10));
73
+ } }
74
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_Template(rf, ctx) { if (rf & 1) {
75
+ i0.ɵɵrepeaterCreate(0, MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_For_1_Template, 1, 6, "ng-container", null, _forTrack0);
76
+ } if (rf & 2) {
77
+ const item_r6 = i0.ɵɵnextContext(2).item;
78
+ i0.ɵɵrepeater(item_r6.children);
79
+ } }
80
+ function MJLeftNavComponent_ng_template_5_Conditional_0_Template(rf, ctx) { if (rf & 1) {
81
+ i0.ɵɵelementStart(0, "div", 5);
82
+ i0.ɵɵconditionalCreate(1, MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_1_Template, 2, 6, "button", 6)(2, MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_2_Template, 1, 0, "span", 7);
83
+ i0.ɵɵtemplate(3, MJLeftNavComponent_ng_template_5_Conditional_0_ng_container_3_Template, 1, 0, "ng-container", 4);
84
+ i0.ɵɵelementEnd();
85
+ i0.ɵɵconditionalCreate(4, MJLeftNavComponent_ng_template_5_Conditional_0_Conditional_4_Template, 2, 0);
86
+ } if (rf & 2) {
87
+ const ctx_r7 = i0.ɵɵnextContext();
88
+ const item_r6 = ctx_r7.item;
89
+ const depth_r9 = ctx_r7.depth;
90
+ const ctx_r2 = i0.ɵɵnextContext();
91
+ const itemBtn_r11 = i0.ɵɵreference(8);
92
+ i0.ɵɵstyleProp("padding-left", depth_r9 * 14, "px");
93
+ i0.ɵɵadvance();
94
+ i0.ɵɵconditional(item_r6.children.length > 0 ? 1 : 2);
95
+ i0.ɵɵadvance(2);
96
+ i0.ɵɵproperty("ngTemplateOutlet", itemBtn_r11)("ngTemplateOutletContext", i0.ɵɵpureFunction1(6, _c3, item_r6));
97
+ i0.ɵɵadvance();
98
+ i0.ɵɵconditional(item_r6.children.length > 0 && ctx_r2.IsExpanded(item_r6.id) ? 4 : -1);
99
+ } }
100
+ function MJLeftNavComponent_ng_template_5_Conditional_1_ng_container_0_Template(rf, ctx) { if (rf & 1) {
101
+ i0.ɵɵelementContainer(0);
102
+ } }
103
+ function MJLeftNavComponent_ng_template_5_Conditional_1_Template(rf, ctx) { if (rf & 1) {
104
+ i0.ɵɵtemplate(0, MJLeftNavComponent_ng_template_5_Conditional_1_ng_container_0_Template, 1, 0, "ng-container", 4);
105
+ } if (rf & 2) {
106
+ const item_r6 = i0.ɵɵnextContext().item;
107
+ i0.ɵɵnextContext();
108
+ const itemBtn_r11 = i0.ɵɵreference(8);
109
+ i0.ɵɵproperty("ngTemplateOutlet", itemBtn_r11)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c3, item_r6));
110
+ } }
111
+ function MJLeftNavComponent_ng_template_5_Template(rf, ctx) { if (rf & 1) {
112
+ i0.ɵɵconditionalCreate(0, MJLeftNavComponent_ng_template_5_Conditional_0_Template, 5, 8)(1, MJLeftNavComponent_ng_template_5_Conditional_1_Template, 1, 4, "ng-container");
113
+ } if (rf & 2) {
114
+ const item_r6 = ctx.item;
115
+ const hasExpandable_r10 = ctx.hasExpandable;
116
+ i0.ɵɵconditional(item_r6.children !== undefined && hasExpandable_r10 ? 0 : 1);
117
+ } }
118
+ function MJLeftNavComponent_ng_template_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
119
+ i0.ɵɵelement(0, "i", 16);
120
+ } if (rf & 2) {
121
+ const item_r13 = i0.ɵɵnextContext().item;
122
+ i0.ɵɵclassMap(item_r13.icon);
123
+ } }
124
+ function MJLeftNavComponent_ng_template_7_Conditional_5_Template(rf, ctx) { if (rf & 1) {
125
+ i0.ɵɵelementStart(0, "span", 14);
126
+ i0.ɵɵtext(1);
127
+ i0.ɵɵelementEnd();
128
+ } if (rf & 2) {
129
+ const item_r13 = i0.ɵɵnextContext().item;
130
+ i0.ɵɵadvance();
131
+ i0.ɵɵtextInterpolate(item_r13.description);
132
+ } }
133
+ function MJLeftNavComponent_ng_template_7_Conditional_6_Template(rf, ctx) { if (rf & 1) {
134
+ i0.ɵɵelementStart(0, "span", 15);
135
+ i0.ɵɵtext(1);
136
+ i0.ɵɵelementEnd();
137
+ } if (rf & 2) {
138
+ const item_r13 = i0.ɵɵnextContext().item;
139
+ i0.ɵɵadvance();
140
+ i0.ɵɵtextInterpolate(item_r13.badge);
141
+ } }
142
+ function MJLeftNavComponent_ng_template_7_Template(rf, ctx) { if (rf & 1) {
143
+ const _r12 = i0.ɵɵgetCurrentView();
144
+ i0.ɵɵelementStart(0, "button", 10);
145
+ i0.ɵɵlistener("click", function MJLeftNavComponent_ng_template_7_Template_button_click_0_listener() { const item_r13 = i0.ɵɵrestoreView(_r12).item; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnItemClick(item_r13)); });
146
+ i0.ɵɵconditionalCreate(1, MJLeftNavComponent_ng_template_7_Conditional_1_Template, 1, 2, "i", 11);
147
+ i0.ɵɵelementStart(2, "span", 12)(3, "span", 13);
148
+ i0.ɵɵtext(4);
149
+ i0.ɵɵelementEnd();
150
+ i0.ɵɵconditionalCreate(5, MJLeftNavComponent_ng_template_7_Conditional_5_Template, 2, 1, "span", 14);
151
+ i0.ɵɵelementEnd();
152
+ i0.ɵɵconditionalCreate(6, MJLeftNavComponent_ng_template_7_Conditional_6_Template, 2, 1, "span", 15);
153
+ i0.ɵɵelementEnd();
154
+ } if (rf & 2) {
155
+ const item_r13 = ctx.item;
156
+ const ctx_r2 = i0.ɵɵnextContext();
157
+ i0.ɵɵclassProp("mj-left-nav__item--active", item_r13.id === ctx_r2.ActiveId)("mj-left-nav__item--disabled", item_r13.disabled);
158
+ i0.ɵɵproperty("disabled", item_r13.disabled);
159
+ i0.ɵɵattribute("aria-current", item_r13.id === ctx_r2.ActiveId ? "page" : null);
160
+ i0.ɵɵadvance();
161
+ i0.ɵɵconditional(item_r13.icon ? 1 : -1);
162
+ i0.ɵɵadvance(3);
163
+ i0.ɵɵtextInterpolate(item_r13.label);
164
+ i0.ɵɵadvance();
165
+ i0.ɵɵconditional(item_r13.description ? 5 : -1);
166
+ i0.ɵɵadvance();
167
+ i0.ɵɵconditional(item_r13.badge != null ? 6 : -1);
168
+ } }
169
+ /**
170
+ * `<mj-left-nav>` — Canonical left rail for MJ Explorer dashboards that have
171
+ * an internal section-nav (Admin shells, KH Config, KH Analytics, AI Analytics,
172
+ * Communication, Credentials, APIKeys, etc.).
173
+ *
174
+ * Replaces the bespoke `.{name}-nav` / `.{name}-nav-item` patterns each of
175
+ * those dashboards used to declare in their own CSS files.
176
+ *
177
+ * Supports:
178
+ * - Plain items (icon + label)
179
+ * - Items with a muted secondary description line (Admin shells pattern)
180
+ * - Items with a count badge (APIKeys pattern)
181
+ * - Sections with uppercase headers (Communication / Credentials pattern)
182
+ * - Optional `[header]` and `[footer]` content slots (KH Config logo header, etc.)
183
+ * - Responsive collapse-to-row at narrow viewports (consumer controls parent's
184
+ * flex-direction switch; the rail's own styles handle item wrapping + the
185
+ * description-hide behavior)
186
+ *
187
+ * ## Example
188
+ *
189
+ * ```typescript
190
+ * sections: MJLeftNavSection[] = [
191
+ * {
192
+ * items: [
193
+ * { id: 'users', icon: 'fa-solid fa-users', label: 'Users', description: 'Manage user accounts' },
194
+ * { id: 'roles', icon: 'fa-solid fa-user-shield', label: 'Roles', description: 'Define roles and assignments' },
195
+ * ]
196
+ * }
197
+ * ];
198
+ * activeId = 'users';
199
+ * ```
200
+ *
201
+ * ```html
202
+ * <mj-left-nav
203
+ * [Sections]="sections"
204
+ * [ActiveId]="activeId"
205
+ * (ItemClicked)="onItemClicked($event)">
206
+ * </mj-left-nav>
207
+ * ```
208
+ *
209
+ * Responsive collapse — the rail's own CSS handles narrow-viewport item
210
+ * wrapping. The consumer is responsible for switching the parent's
211
+ * flex-direction from row → column at the same breakpoint, e.g. via
212
+ * `<mj-page-body>` Direction="row" with a `@media` override in the shell's CSS.
213
+ */
214
+ export class MJLeftNavComponent {
215
+ /**
216
+ * The sections + items that make up the rail. Sections render top-to-bottom
217
+ * in array order; items inside each section render in array order.
218
+ */
219
+ Sections = [];
220
+ /**
221
+ * The id of the currently active item. Items matching this id get the
222
+ * active styling and `aria-current="page"`.
223
+ */
224
+ ActiveId = null;
225
+ /**
226
+ * Width of the rail in pixels. Defaults to 240. Set to a smaller value
227
+ * (e.g., 200) for denser shells.
228
+ */
229
+ Width = 240;
230
+ /**
231
+ * Ids of currently-expanded tree items. Items whose id appears here render
232
+ * with a down-chevron and their `children` are visible; items not in this
233
+ * list render with a right-chevron and their children stay collapsed.
234
+ * Consumer owns the state — toggle in response to `(ItemToggled)`.
235
+ *
236
+ * Only applies to items where `children !== undefined`; flat items ignore it.
237
+ */
238
+ ExpandedIds = [];
239
+ /**
240
+ * Emitted when a non-disabled item is clicked.
241
+ */
242
+ ItemClicked = new EventEmitter();
243
+ /**
244
+ * Emitted when a tree item's chevron is clicked. Consumer should toggle
245
+ * the item's id in / out of `ExpandedIds`. Does NOT also fire `ItemClicked`.
246
+ */
247
+ ItemToggled = new EventEmitter();
248
+ OnItemClick(item) {
249
+ if (item.disabled)
250
+ return;
251
+ this.ItemClicked.emit(item);
252
+ }
253
+ OnItemToggle(event, item) {
254
+ event.stopPropagation();
255
+ this.ItemToggled.emit(item);
256
+ }
257
+ IsExpanded(id) {
258
+ return this.ExpandedIds.indexOf(id) >= 0;
259
+ }
260
+ /**
261
+ * Whether any top-level item in this section actually has children to expand.
262
+ * The template calls this once per section; the result is threaded through
263
+ * the recursive itemTpl so leaf nodes only reserve a chevron-column
264
+ * placeholder when a sibling will actually render a chevron.
265
+ *
266
+ * Top-level check is sufficient — if a deeply-nested item has children, its
267
+ * parent's `children.length > 0` already qualifies the section as expandable.
268
+ */
269
+ HasExpandableItems(items) {
270
+ for (const item of items) {
271
+ if (item.children && item.children.length > 0)
272
+ return true;
273
+ }
274
+ return false;
275
+ }
276
+ static ɵfac = function MJLeftNavComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJLeftNavComponent)(); };
277
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJLeftNavComponent, selectors: [["mj-left-nav"]], inputs: { Sections: "Sections", ActiveId: "ActiveId", Width: "Width", ExpandedIds: "ExpandedIds" }, outputs: { ItemClicked: "ItemClicked", ItemToggled: "ItemToggled" }, ngContentSelectors: _c1, decls: 9, vars: 2, consts: [["itemTpl", ""], ["itemBtn", ""], ["role", "navigation", 1, "mj-left-nav"], [1, "mj-left-nav__section-label"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"], [1, "mj-left-nav__row"], ["type", "button", 1, "mj-left-nav__chevron"], ["aria-hidden", "true", 1, "mj-left-nav__chevron-placeholder"], ["type", "button", 1, "mj-left-nav__chevron", 3, "click"], ["aria-hidden", "true", 1, "fa-solid"], ["type", "button", 1, "mj-left-nav__item", 3, "click", "disabled"], ["aria-hidden", "true", 1, "mj-left-nav__icon", 3, "class"], [1, "mj-left-nav__text"], [1, "mj-left-nav__label"], [1, "mj-left-nav__description"], [1, "mj-left-nav__badge"], ["aria-hidden", "true", 1, "mj-left-nav__icon"]], template: function MJLeftNavComponent_Template(rf, ctx) { if (rf & 1) {
278
+ i0.ɵɵprojectionDef(_c0);
279
+ i0.ɵɵelementStart(0, "aside", 2);
280
+ i0.ɵɵprojection(1);
281
+ i0.ɵɵrepeaterCreate(2, MJLeftNavComponent_For_3_Template, 3, 1, null, null, i0.ɵɵrepeaterTrackByIndex);
282
+ i0.ɵɵprojection(4, 1);
283
+ i0.ɵɵelementEnd();
284
+ i0.ɵɵtemplate(5, MJLeftNavComponent_ng_template_5_Template, 2, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor)(7, MJLeftNavComponent_ng_template_7_Template, 7, 10, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
285
+ } if (rf & 2) {
286
+ i0.ɵɵstyleProp("width", ctx.Width, "px");
287
+ i0.ɵɵadvance(2);
288
+ i0.ɵɵrepeater(ctx.Sections);
289
+ } }, dependencies: [CommonModule, i1.NgTemplateOutlet], styles: ["[_nghost-%COMP%] {\n display: block;\n flex-shrink: 0;\n }\n\n .mj-left-nav[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n overflow-y: auto;\n padding: 8px;\n box-sizing: border-box;\n }\n\n .mj-left-nav__section-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n padding: 12px 12px 6px;\n }\n\n .mj-left-nav__section-label[_ngcontent-%COMP%]:first-child {\n padding-top: 6px;\n }\n\n .mj-left-nav__item[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n width: 100%;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n margin-bottom: 2px;\n font-family: inherit;\n color: var(--mj-text-secondary);\n text-align: left;\n }\n\n .mj-left-nav__item[_ngcontent-%COMP%]:hover:not(.mj-left-nav__item--disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__item--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n }\n\n .mj-left-nav__item--active[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n }\n\n .mj-left-nav__item--disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .mj-left-nav__icon[_ngcontent-%COMP%] {\n width: 18px;\n text-align: center;\n font-size: 13px;\n margin-top: 1px;\n flex-shrink: 0;\n color: inherit;\n }\n\n .mj-left-nav__text[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n }\n\n .mj-left-nav__label[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n line-height: 1.2;\n }\n\n .mj-left-nav__description[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 3px;\n line-height: 1.3;\n }\n\n .mj-left-nav__item--active[_ngcontent-%COMP%] .mj-left-nav__description[_ngcontent-%COMP%] {\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n }\n\n .mj-left-nav__badge[_ngcontent-%COMP%] {\n margin-left: auto;\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n align-self: center;\n }\n\n .mj-left-nav__item--active[_ngcontent-%COMP%] .mj-left-nav__badge[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n color: var(--mj-brand-primary);\n }\n\n \n\n\n\n\n\n\n .mj-left-nav__row[_ngcontent-%COMP%] {\n display: flex;\n align-items: stretch;\n gap: 2px;\n }\n\n .mj-left-nav__row[_ngcontent-%COMP%] .mj-left-nav__item[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n\n .mj-left-nav__chevron[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 20px;\n height: 32px;\n align-self: center;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 10px;\n padding: 0;\n transition: background-color 0.12s ease, color 0.12s ease;\n font-family: inherit;\n }\n\n .mj-left-nav__chevron[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__chevron-placeholder[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 20px;\n display: inline-block;\n }\n\n \n\n\n\n\n @media (max-width: 700px) {\n [_nghost-%COMP%] {\n width: 100% !important;\n }\n .mj-left-nav[_ngcontent-%COMP%] {\n width: 100%;\n height: auto;\n max-height: 220px;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .mj-left-nav__item[_ngcontent-%COMP%] {\n flex: 1 1 calc(50% - 8px);\n margin-bottom: 0;\n }\n .mj-left-nav__description[_ngcontent-%COMP%] {\n display: none;\n }\n .mj-left-nav__section-label[_ngcontent-%COMP%] {\n flex-basis: 100%;\n }\n \n\n\n .mj-left-nav__row[_ngcontent-%COMP%] {\n flex-basis: 100%;\n padding-left: 0 !important;\n }\n }"] });
290
+ }
291
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJLeftNavComponent, [{
292
+ type: Component,
293
+ args: [{ selector: 'mj-left-nav', standalone: true, imports: [CommonModule], template: `
294
+ <aside class="mj-left-nav" [style.width.px]="Width" role="navigation">
295
+ <ng-content select="[header]"></ng-content>
296
+
297
+ @for (section of Sections; track $index) {
298
+ @if (section.label) {
299
+ <div class="mj-left-nav__section-label">{{ section.label }}</div>
300
+ }
301
+ @for (item of section.items; track item.id) {
302
+ <ng-container
303
+ *ngTemplateOutlet="itemTpl; context: { item: item, depth: 0, hasExpandable: HasExpandableItems(section.items) }">
304
+ </ng-container>
305
+ }
306
+ }
307
+
308
+ <ng-content select="[footer]"></ng-content>
309
+ </aside>
310
+
311
+ <!-- Recursive item template — renders one item plus, when expanded,
312
+ its children indented by depth. The hasExpandable flag is computed
313
+ once per section and threaded through the recursion so leaf nodes
314
+ only reserve a chevron-column placeholder when a sibling actually
315
+ shows a chevron. -->
316
+ <ng-template #itemTpl let-item="item" let-depth="depth" let-hasExpandable="hasExpandable">
317
+ @if (item.children !== undefined && hasExpandable) {
318
+ <!-- Tree row: chevron (when this node has children) or placeholder
319
+ (when a sibling has children but this leaf needs alignment). -->
320
+ <div
321
+ class="mj-left-nav__row"
322
+ [style.padding-left.px]="depth * 14">
323
+ @if (item.children.length > 0) {
324
+ <button
325
+ type="button"
326
+ class="mj-left-nav__chevron"
327
+ [attr.aria-label]="IsExpanded(item.id) ? 'Collapse' : 'Expand'"
328
+ [attr.aria-expanded]="IsExpanded(item.id)"
329
+ (click)="OnItemToggle($event, item)">
330
+ <i class="fa-solid"
331
+ [class.fa-chevron-down]="IsExpanded(item.id)"
332
+ [class.fa-chevron-right]="!IsExpanded(item.id)"
333
+ aria-hidden="true"></i>
334
+ </button>
335
+ } @else {
336
+ <span class="mj-left-nav__chevron-placeholder" aria-hidden="true"></span>
337
+ }
338
+ <ng-container
339
+ *ngTemplateOutlet="itemBtn; context: { item: item }">
340
+ </ng-container>
341
+ </div>
342
+
343
+ @if (item.children.length > 0 && IsExpanded(item.id)) {
344
+ @for (child of item.children; track child.id) {
345
+ <ng-container
346
+ *ngTemplateOutlet="itemTpl; context: { item: child, depth: depth + 1, hasExpandable: hasExpandable }">
347
+ </ng-container>
348
+ }
349
+ }
350
+ } @else {
351
+ <!-- Flat render: item.children is undefined, OR no sibling in this
352
+ section is actually expandable — render the button directly with
353
+ no chevron column reserved. -->
354
+ <ng-container
355
+ *ngTemplateOutlet="itemBtn; context: { item: item }">
356
+ </ng-container>
357
+ }
358
+ </ng-template>
359
+
360
+ <!-- Item button — shared by both flat and tree rendering paths -->
361
+ <ng-template #itemBtn let-item="item">
362
+ <button
363
+ type="button"
364
+ class="mj-left-nav__item"
365
+ [class.mj-left-nav__item--active]="item.id === ActiveId"
366
+ [class.mj-left-nav__item--disabled]="item.disabled"
367
+ [disabled]="item.disabled"
368
+ [attr.aria-current]="item.id === ActiveId ? 'page' : null"
369
+ (click)="OnItemClick(item)">
370
+ @if (item.icon) {
371
+ <i class="mj-left-nav__icon" [class]="item.icon" aria-hidden="true"></i>
372
+ }
373
+ <span class="mj-left-nav__text">
374
+ <span class="mj-left-nav__label">{{ item.label }}</span>
375
+ @if (item.description) {
376
+ <span class="mj-left-nav__description">{{ item.description }}</span>
377
+ }
378
+ </span>
379
+ @if (item.badge != null) {
380
+ <span class="mj-left-nav__badge">{{ item.badge }}</span>
381
+ }
382
+ </button>
383
+ </ng-template>
384
+ `, styles: ["\n :host {\n display: block;\n flex-shrink: 0;\n }\n\n .mj-left-nav {\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n overflow-y: auto;\n padding: 8px;\n box-sizing: border-box;\n }\n\n .mj-left-nav__section-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n padding: 12px 12px 6px;\n }\n\n .mj-left-nav__section-label:first-child {\n padding-top: 6px;\n }\n\n .mj-left-nav__item {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n width: 100%;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n margin-bottom: 2px;\n font-family: inherit;\n color: var(--mj-text-secondary);\n text-align: left;\n }\n\n .mj-left-nav__item:hover:not(.mj-left-nav__item--disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__item--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n }\n\n .mj-left-nav__item--active:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n }\n\n .mj-left-nav__item--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .mj-left-nav__icon {\n width: 18px;\n text-align: center;\n font-size: 13px;\n margin-top: 1px;\n flex-shrink: 0;\n color: inherit;\n }\n\n .mj-left-nav__text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n }\n\n .mj-left-nav__label {\n font-size: 13px;\n font-weight: 500;\n line-height: 1.2;\n }\n\n .mj-left-nav__description {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 3px;\n line-height: 1.3;\n }\n\n .mj-left-nav__item--active .mj-left-nav__description {\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n }\n\n .mj-left-nav__badge {\n margin-left: auto;\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n align-self: center;\n }\n\n .mj-left-nav__item--active .mj-left-nav__badge {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n color: var(--mj-brand-primary);\n }\n\n /* \u2500\u2500 Tree rendering \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Used when MJLeftNavItem.children is defined (even as an empty array).\n The row wraps a chevron/placeholder + the item button so they sit on\n one line, and depth-based padding-left is applied to the row (not the\n item) so the chevron aligns with the depth column. */\n\n .mj-left-nav__row {\n display: flex;\n align-items: stretch;\n gap: 2px;\n }\n\n .mj-left-nav__row .mj-left-nav__item {\n flex: 1;\n min-width: 0;\n }\n\n .mj-left-nav__chevron {\n flex-shrink: 0;\n width: 20px;\n height: 32px;\n align-self: center;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 10px;\n padding: 0;\n transition: background-color 0.12s ease, color 0.12s ease;\n font-family: inherit;\n }\n\n .mj-left-nav__chevron:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__chevron-placeholder {\n flex-shrink: 0;\n width: 20px;\n display: inline-block;\n }\n\n /* Responsive collapse: at narrow viewports the rail becomes a top bar of\n wrapping items. The consumer is still responsible for switching the\n parent's flex-direction from row \u2192 column (see <mj-page-body> Direction\n + the shell's @media override). */\n @media (max-width: 700px) {\n :host {\n width: 100% !important;\n }\n .mj-left-nav {\n width: 100%;\n height: auto;\n max-height: 220px;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .mj-left-nav__item {\n flex: 1 1 calc(50% - 8px);\n margin-bottom: 0;\n }\n .mj-left-nav__description {\n display: none;\n }\n .mj-left-nav__section-label {\n flex-basis: 100%;\n }\n /* Tree rows take a full line even in the wrap layout \u2014 chevron + item\n should stay paired rather than flow as separate flex children. */\n .mj-left-nav__row {\n flex-basis: 100%;\n padding-left: 0 !important;\n }\n }\n "] }]
385
+ }], null, { Sections: [{
386
+ type: Input
387
+ }], ActiveId: [{
388
+ type: Input
389
+ }], Width: [{
390
+ type: Input
391
+ }], ExpandedIds: [{
392
+ type: Input
393
+ }], ItemClicked: [{
394
+ type: Output
395
+ }], ItemToggled: [{
396
+ type: Output
397
+ }] }); })();
398
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJLeftNavComponent, { className: "MJLeftNavComponent", filePath: "lib/left-nav/left-nav.component.ts", lineNumber: 383 }); })();
399
+ //# sourceMappingURL=left-nav.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"left-nav.component.js","sourceRoot":"","sources":["../../../src/lib/left-nav/left-nav.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * A single item in an `<mj-left-nav>`.\n */\nexport interface MJLeftNavItem {\n /** Stable identifier — used for active-state matching and emitted on click. */\n id: string;\n /** Primary label rendered as the item's main text. */\n label: string;\n /** Optional Font Awesome icon class (e.g. `'fa-solid fa-users'`). */\n icon?: string;\n /** Optional muted secondary line shown under the label. */\n description?: string;\n /** Optional count badge rendered at the right of the item. */\n badge?: string | number;\n /** When `true`, the item renders dimmed and is not clickable. */\n disabled?: boolean;\n /**\n * Optional child items for tree-structured nav (e.g. Testing Explorer's\n * Test Suites tree). When `children` is `undefined` the item renders as a\n * plain flat-list item (existing behavior). When defined — even as an empty\n * array — the item participates in tree rendering: a chevron toggle shows\n * for non-empty arrays, and an equal-width placeholder reserves alignment\n * space for leaves so siblings line up. Expansion state is owned by the\n * consumer via `[ExpandedIds]` on `<mj-left-nav>`.\n */\n children?: MJLeftNavItem[];\n}\n\n/**\n * A grouped section in an `<mj-left-nav>`. The optional `label` renders as an\n * uppercase section header above the items. Sections WITHOUT a label still\n * group items together (use multiple unlabeled sections to get visual dividers\n * without headers).\n */\nexport interface MJLeftNavSection {\n /** Optional uppercase header above the items in this section. */\n label?: string;\n /** The clickable items in this section. */\n items: MJLeftNavItem[];\n}\n\n/**\n * `<mj-left-nav>` — Canonical left rail for MJ Explorer dashboards that have\n * an internal section-nav (Admin shells, KH Config, KH Analytics, AI Analytics,\n * Communication, Credentials, APIKeys, etc.).\n *\n * Replaces the bespoke `.{name}-nav` / `.{name}-nav-item` patterns each of\n * those dashboards used to declare in their own CSS files.\n *\n * Supports:\n * - Plain items (icon + label)\n * - Items with a muted secondary description line (Admin shells pattern)\n * - Items with a count badge (APIKeys pattern)\n * - Sections with uppercase headers (Communication / Credentials pattern)\n * - Optional `[header]` and `[footer]` content slots (KH Config logo header, etc.)\n * - Responsive collapse-to-row at narrow viewports (consumer controls parent's\n * flex-direction switch; the rail's own styles handle item wrapping + the\n * description-hide behavior)\n *\n * ## Example\n *\n * ```typescript\n * sections: MJLeftNavSection[] = [\n * {\n * items: [\n * { id: 'users', icon: 'fa-solid fa-users', label: 'Users', description: 'Manage user accounts' },\n * { id: 'roles', icon: 'fa-solid fa-user-shield', label: 'Roles', description: 'Define roles and assignments' },\n * ]\n * }\n * ];\n * activeId = 'users';\n * ```\n *\n * ```html\n * <mj-left-nav\n * [Sections]=\"sections\"\n * [ActiveId]=\"activeId\"\n * (ItemClicked)=\"onItemClicked($event)\">\n * </mj-left-nav>\n * ```\n *\n * Responsive collapse — the rail's own CSS handles narrow-viewport item\n * wrapping. The consumer is responsible for switching the parent's\n * flex-direction from row → column at the same breakpoint, e.g. via\n * `<mj-page-body>` Direction=\"row\" with a `@media` override in the shell's CSS.\n */\n@Component({\n selector: 'mj-left-nav',\n standalone: true,\n imports: [CommonModule],\n template: `\n <aside class=\"mj-left-nav\" [style.width.px]=\"Width\" role=\"navigation\">\n <ng-content select=\"[header]\"></ng-content>\n\n @for (section of Sections; track $index) {\n @if (section.label) {\n <div class=\"mj-left-nav__section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <ng-container\n *ngTemplateOutlet=\"itemTpl; context: { item: item, depth: 0, hasExpandable: HasExpandableItems(section.items) }\">\n </ng-container>\n }\n }\n\n <ng-content select=\"[footer]\"></ng-content>\n </aside>\n\n <!-- Recursive item template — renders one item plus, when expanded,\n its children indented by depth. The hasExpandable flag is computed\n once per section and threaded through the recursion so leaf nodes\n only reserve a chevron-column placeholder when a sibling actually\n shows a chevron. -->\n <ng-template #itemTpl let-item=\"item\" let-depth=\"depth\" let-hasExpandable=\"hasExpandable\">\n @if (item.children !== undefined && hasExpandable) {\n <!-- Tree row: chevron (when this node has children) or placeholder\n (when a sibling has children but this leaf needs alignment). -->\n <div\n class=\"mj-left-nav__row\"\n [style.padding-left.px]=\"depth * 14\">\n @if (item.children.length > 0) {\n <button\n type=\"button\"\n class=\"mj-left-nav__chevron\"\n [attr.aria-label]=\"IsExpanded(item.id) ? 'Collapse' : 'Expand'\"\n [attr.aria-expanded]=\"IsExpanded(item.id)\"\n (click)=\"OnItemToggle($event, item)\">\n <i class=\"fa-solid\"\n [class.fa-chevron-down]=\"IsExpanded(item.id)\"\n [class.fa-chevron-right]=\"!IsExpanded(item.id)\"\n aria-hidden=\"true\"></i>\n </button>\n } @else {\n <span class=\"mj-left-nav__chevron-placeholder\" aria-hidden=\"true\"></span>\n }\n <ng-container\n *ngTemplateOutlet=\"itemBtn; context: { item: item }\">\n </ng-container>\n </div>\n\n @if (item.children.length > 0 && IsExpanded(item.id)) {\n @for (child of item.children; track child.id) {\n <ng-container\n *ngTemplateOutlet=\"itemTpl; context: { item: child, depth: depth + 1, hasExpandable: hasExpandable }\">\n </ng-container>\n }\n }\n } @else {\n <!-- Flat render: item.children is undefined, OR no sibling in this\n section is actually expandable — render the button directly with\n no chevron column reserved. -->\n <ng-container\n *ngTemplateOutlet=\"itemBtn; context: { item: item }\">\n </ng-container>\n }\n </ng-template>\n\n <!-- Item button — shared by both flat and tree rendering paths -->\n <ng-template #itemBtn let-item=\"item\">\n <button\n type=\"button\"\n class=\"mj-left-nav__item\"\n [class.mj-left-nav__item--active]=\"item.id === ActiveId\"\n [class.mj-left-nav__item--disabled]=\"item.disabled\"\n [disabled]=\"item.disabled\"\n [attr.aria-current]=\"item.id === ActiveId ? 'page' : null\"\n (click)=\"OnItemClick(item)\">\n @if (item.icon) {\n <i class=\"mj-left-nav__icon\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"mj-left-nav__text\">\n <span class=\"mj-left-nav__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"mj-left-nav__description\">{{ item.description }}</span>\n }\n </span>\n @if (item.badge != null) {\n <span class=\"mj-left-nav__badge\">{{ item.badge }}</span>\n }\n </button>\n </ng-template>\n `,\n styles: [`\n :host {\n display: block;\n flex-shrink: 0;\n }\n\n .mj-left-nav {\n height: 100%;\n background: var(--mj-bg-surface);\n border-right: 1px solid var(--mj-border-default);\n overflow-y: auto;\n padding: 8px;\n box-sizing: border-box;\n }\n\n .mj-left-nav__section-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n padding: 12px 12px 6px;\n }\n\n .mj-left-nav__section-label:first-child {\n padding-top: 6px;\n }\n\n .mj-left-nav__item {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n width: 100%;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n margin-bottom: 2px;\n font-family: inherit;\n color: var(--mj-text-secondary);\n text-align: left;\n }\n\n .mj-left-nav__item:hover:not(.mj-left-nav__item--disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__item--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n }\n\n .mj-left-nav__item--active:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n }\n\n .mj-left-nav__item--disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .mj-left-nav__icon {\n width: 18px;\n text-align: center;\n font-size: 13px;\n margin-top: 1px;\n flex-shrink: 0;\n color: inherit;\n }\n\n .mj-left-nav__text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n }\n\n .mj-left-nav__label {\n font-size: 13px;\n font-weight: 500;\n line-height: 1.2;\n }\n\n .mj-left-nav__description {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 3px;\n line-height: 1.3;\n }\n\n .mj-left-nav__item--active .mj-left-nav__description {\n color: color-mix(in srgb, var(--mj-brand-primary) 70%, var(--mj-text-muted));\n }\n\n .mj-left-nav__badge {\n margin-left: auto;\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n align-self: center;\n }\n\n .mj-left-nav__item--active .mj-left-nav__badge {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n color: var(--mj-brand-primary);\n }\n\n /* ── Tree rendering ─────────────────────────────────────────────────────\n Used when MJLeftNavItem.children is defined (even as an empty array).\n The row wraps a chevron/placeholder + the item button so they sit on\n one line, and depth-based padding-left is applied to the row (not the\n item) so the chevron aligns with the depth column. */\n\n .mj-left-nav__row {\n display: flex;\n align-items: stretch;\n gap: 2px;\n }\n\n .mj-left-nav__row .mj-left-nav__item {\n flex: 1;\n min-width: 0;\n }\n\n .mj-left-nav__chevron {\n flex-shrink: 0;\n width: 20px;\n height: 32px;\n align-self: center;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 10px;\n padding: 0;\n transition: background-color 0.12s ease, color 0.12s ease;\n font-family: inherit;\n }\n\n .mj-left-nav__chevron:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .mj-left-nav__chevron-placeholder {\n flex-shrink: 0;\n width: 20px;\n display: inline-block;\n }\n\n /* Responsive collapse: at narrow viewports the rail becomes a top bar of\n wrapping items. The consumer is still responsible for switching the\n parent's flex-direction from row → column (see <mj-page-body> Direction\n + the shell's @media override). */\n @media (max-width: 700px) {\n :host {\n width: 100% !important;\n }\n .mj-left-nav {\n width: 100%;\n height: auto;\n max-height: 220px;\n border-right: none;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .mj-left-nav__item {\n flex: 1 1 calc(50% - 8px);\n margin-bottom: 0;\n }\n .mj-left-nav__description {\n display: none;\n }\n .mj-left-nav__section-label {\n flex-basis: 100%;\n }\n /* Tree rows take a full line even in the wrap layout — chevron + item\n should stay paired rather than flow as separate flex children. */\n .mj-left-nav__row {\n flex-basis: 100%;\n padding-left: 0 !important;\n }\n }\n `]\n})\nexport class MJLeftNavComponent {\n /**\n * The sections + items that make up the rail. Sections render top-to-bottom\n * in array order; items inside each section render in array order.\n */\n @Input() Sections: MJLeftNavSection[] = [];\n\n /**\n * The id of the currently active item. Items matching this id get the\n * active styling and `aria-current=\"page\"`.\n */\n @Input() ActiveId: string | null = null;\n\n /**\n * Width of the rail in pixels. Defaults to 240. Set to a smaller value\n * (e.g., 200) for denser shells.\n */\n @Input() Width: number = 240;\n\n /**\n * Ids of currently-expanded tree items. Items whose id appears here render\n * with a down-chevron and their `children` are visible; items not in this\n * list render with a right-chevron and their children stay collapsed.\n * Consumer owns the state — toggle in response to `(ItemToggled)`.\n *\n * Only applies to items where `children !== undefined`; flat items ignore it.\n */\n @Input() ExpandedIds: string[] = [];\n\n /**\n * Emitted when a non-disabled item is clicked.\n */\n @Output() ItemClicked = new EventEmitter<MJLeftNavItem>();\n\n /**\n * Emitted when a tree item's chevron is clicked. Consumer should toggle\n * the item's id in / out of `ExpandedIds`. Does NOT also fire `ItemClicked`.\n */\n @Output() ItemToggled = new EventEmitter<MJLeftNavItem>();\n\n OnItemClick(item: MJLeftNavItem): void {\n if (item.disabled) return;\n this.ItemClicked.emit(item);\n }\n\n OnItemToggle(event: Event, item: MJLeftNavItem): void {\n event.stopPropagation();\n this.ItemToggled.emit(item);\n }\n\n IsExpanded(id: string): boolean {\n return this.ExpandedIds.indexOf(id) >= 0;\n }\n\n /**\n * Whether any top-level item in this section actually has children to expand.\n * The template calls this once per section; the result is threaded through\n * the recursive itemTpl so leaf nodes only reserve a chevron-column\n * placeholder when a sibling will actually render a chevron.\n *\n * Top-level check is sufficient — if a deeply-nested item has children, its\n * parent's `children.length > 0` already qualifies the section as expandable.\n */\n HasExpandableItems(items: MJLeftNavItem[]): boolean {\n for (const item of items) {\n if (item.children && item.children.length > 0) return true;\n }\n return false;\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * mj-page-body — Standard body region for MJ Explorer dashboards / resource pages.
4
+ *
5
+ * Sits inside `<mj-page-layout>` after `<mj-page-header>`. Provides the canonical
6
+ * page body recipe: `flex: 1`, `min-height: 0`, padded gutters, vertical scroll.
7
+ *
8
+ * Escape hatches:
9
+ * - `[Padding]="false"` — remove the 24px gutter (e.g., pages whose inner
10
+ * content owns the gutter itself, like File Browser or AI Analytics shell).
11
+ * - `[Flex]="true"` — switch the body to `flex; flex-direction: column;
12
+ * position: relative` so a child marked `flex: 1` (e.g., a main content area
13
+ * that should fill remaining height under a banner row) can grow correctly.
14
+ *
15
+ * Note: overflow-y is intentionally always `auto` and not configurable.
16
+ * `mj-page-layout` already has `overflow: hidden`, so any body content that
17
+ * exceeds the visible area would be silently clipped if the body itself didn't
18
+ * scroll. Pages with split-pane layouts whose children manage their own scroll
19
+ * regions should use `min-height: 0 + flex: 1` on the inner panes so nothing
20
+ * overflows the body — auto stays harmlessly inactive.
21
+ *
22
+ * Example:
23
+ * ```html
24
+ * <mj-page-layout>
25
+ * <mj-page-header Title="Jobs" Icon="fa-solid fa-calendar-check"></mj-page-header>
26
+ * <mj-page-body>
27
+ * <!-- page content -->
28
+ * </mj-page-body>
29
+ * </mj-page-layout>
30
+ * ```
31
+ */
32
+ export declare class MJPageBodyComponent {
33
+ /** When `false`, removes the default 24px gutter. Defaults to `true`. */
34
+ Padding: boolean;
35
+ /**
36
+ * When `true`, switches the body to flex layout (with `position: relative`)
37
+ * so a child marked `flex: 1` can fill the remaining vertical space — e.g., a main
38
+ * content area sitting under a banner row. Defaults to `false` (block flow).
39
+ *
40
+ * Combine with `Direction="row"` to lay out children left-to-right (rail + content).
41
+ */
42
+ Flex: boolean;
43
+ /**
44
+ * When `Flex=true`, controls the flex direction. `'column'` (default) stacks
45
+ * children vertically. `'row'` lays them out left-to-right, useful for
46
+ * left-rail + content shells (Admin's `admin-container`, KH Config, etc.).
47
+ * Ignored when `Flex=false`.
48
+ */
49
+ Direction: 'row' | 'column';
50
+ get NoPaddingClass(): boolean;
51
+ get FlexClass(): boolean;
52
+ get RowClass(): boolean;
53
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJPageBodyComponent, never>;
54
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJPageBodyComponent, "mj-page-body", never, { "Padding": { "alias": "Padding"; "required": false; }; "Flex": { "alias": "Flex"; "required": false; }; "Direction": { "alias": "Direction"; "required": false; }; }, {}, never, ["*"], true, never>;
55
+ }
56
+ //# sourceMappingURL=page-body.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-body.component.d.ts","sourceRoot":"","sources":["../../../src/lib/page-body/page-body.component.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAgCa,mBAAmB;IAC9B,yEAAyE;IAChE,OAAO,EAAE,OAAO,CAAQ;IAEjC;;;;;;OAMG;IACM,IAAI,EAAE,OAAO,CAAS;IAE/B;;;;;OAKG;IACM,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAY;IAEhD,IACI,cAAc,IAAI,OAAO,CAE5B;IAED,IACI,SAAS,IAAI,OAAO,CAEvB;IAED,IACI,QAAQ,IAAI,OAAO,CAEtB;yCAlCU,mBAAmB;2CAAnB,mBAAmB;CAmC/B"}
@@ -0,0 +1,89 @@
1
+ import { Component, HostBinding, Input } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ const _c0 = ["*"];
4
+ /**
5
+ * mj-page-body — Standard body region for MJ Explorer dashboards / resource pages.
6
+ *
7
+ * Sits inside `<mj-page-layout>` after `<mj-page-header>`. Provides the canonical
8
+ * page body recipe: `flex: 1`, `min-height: 0`, padded gutters, vertical scroll.
9
+ *
10
+ * Escape hatches:
11
+ * - `[Padding]="false"` — remove the 24px gutter (e.g., pages whose inner
12
+ * content owns the gutter itself, like File Browser or AI Analytics shell).
13
+ * - `[Flex]="true"` — switch the body to `flex; flex-direction: column;
14
+ * position: relative` so a child marked `flex: 1` (e.g., a main content area
15
+ * that should fill remaining height under a banner row) can grow correctly.
16
+ *
17
+ * Note: overflow-y is intentionally always `auto` and not configurable.
18
+ * `mj-page-layout` already has `overflow: hidden`, so any body content that
19
+ * exceeds the visible area would be silently clipped if the body itself didn't
20
+ * scroll. Pages with split-pane layouts whose children manage their own scroll
21
+ * regions should use `min-height: 0 + flex: 1` on the inner panes so nothing
22
+ * overflows the body — auto stays harmlessly inactive.
23
+ *
24
+ * Example:
25
+ * ```html
26
+ * <mj-page-layout>
27
+ * <mj-page-header Title="Jobs" Icon="fa-solid fa-calendar-check"></mj-page-header>
28
+ * <mj-page-body>
29
+ * <!-- page content -->
30
+ * </mj-page-body>
31
+ * </mj-page-layout>
32
+ * ```
33
+ */
34
+ export class MJPageBodyComponent {
35
+ /** When `false`, removes the default 24px gutter. Defaults to `true`. */
36
+ Padding = true;
37
+ /**
38
+ * When `true`, switches the body to flex layout (with `position: relative`)
39
+ * so a child marked `flex: 1` can fill the remaining vertical space — e.g., a main
40
+ * content area sitting under a banner row. Defaults to `false` (block flow).
41
+ *
42
+ * Combine with `Direction="row"` to lay out children left-to-right (rail + content).
43
+ */
44
+ Flex = false;
45
+ /**
46
+ * When `Flex=true`, controls the flex direction. `'column'` (default) stacks
47
+ * children vertically. `'row'` lays them out left-to-right, useful for
48
+ * left-rail + content shells (Admin's `admin-container`, KH Config, etc.).
49
+ * Ignored when `Flex=false`.
50
+ */
51
+ Direction = 'column';
52
+ get NoPaddingClass() {
53
+ return !this.Padding;
54
+ }
55
+ get FlexClass() {
56
+ return this.Flex;
57
+ }
58
+ get RowClass() {
59
+ return this.Flex && this.Direction === 'row';
60
+ }
61
+ static ɵfac = function MJPageBodyComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJPageBodyComponent)(); };
62
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJPageBodyComponent, selectors: [["mj-page-body"]], hostVars: 6, hostBindings: function MJPageBodyComponent_HostBindings(rf, ctx) { if (rf & 2) {
63
+ i0.ɵɵclassProp("mj-page-body--no-padding", ctx.NoPaddingClass)("mj-page-body--flex", ctx.FlexClass)("mj-page-body--row", ctx.RowClass);
64
+ } }, inputs: { Padding: "Padding", Flex: "Flex", Direction: "Direction" }, ngContentSelectors: _c0, decls: 1, vars: 0, template: function MJPageBodyComponent_Template(rf, ctx) { if (rf & 1) {
65
+ i0.ɵɵprojectionDef();
66
+ i0.ɵɵprojection(0);
67
+ } }, styles: ["[_nghost-%COMP%] {\n display: block;\n flex: 1;\n min-height: 0;\n \n\n\n\n padding: var(--mj-space-6);\n overflow-y: auto;\n }\n .mj-page-body--no-padding[_nghost-%COMP%] {\n padding: 0;\n }\n .mj-page-body--flex[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n position: relative;\n }\n \n\n\n\n\n .mj-page-body--flex.mj-page-body--row[_nghost-%COMP%] {\n flex-direction: row;\n }"] });
68
+ }
69
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJPageBodyComponent, [{
70
+ type: Component,
71
+ args: [{ selector: 'mj-page-body', standalone: true, template: `<ng-content></ng-content>`, styles: ["\n :host {\n display: block;\n flex: 1;\n min-height: 0;\n /* Top padding doubles as header-to-body spacing \u2014 <mj-page-header>\n intentionally has no margin-bottom so [Padding]=\"false\" yields a\n truly flush layout (e.g. AI Analytics sidebar+content). */\n padding: var(--mj-space-6);\n overflow-y: auto;\n }\n :host(.mj-page-body--no-padding) {\n padding: 0;\n }\n :host(.mj-page-body--flex) {\n display: flex;\n flex-direction: column;\n position: relative;\n }\n /* Direction=\"row\" \u2014 switch flex direction when [Flex]=\"true\". Used by\n shells whose body is a left-rail + content layout (Admin shells, KH\n Config, AI Analytics). Replaces the bespoke per-shell-container body\n grid/flex wrapper that every shell used to declare. */\n :host(.mj-page-body--flex.mj-page-body--row) {\n flex-direction: row;\n }\n "] }]
72
+ }], null, { Padding: [{
73
+ type: Input
74
+ }], Flex: [{
75
+ type: Input
76
+ }], Direction: [{
77
+ type: Input
78
+ }], NoPaddingClass: [{
79
+ type: HostBinding,
80
+ args: ['class.mj-page-body--no-padding']
81
+ }], FlexClass: [{
82
+ type: HostBinding,
83
+ args: ['class.mj-page-body--flex']
84
+ }], RowClass: [{
85
+ type: HostBinding,
86
+ args: ['class.mj-page-body--row']
87
+ }] }); })();
88
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJPageBodyComponent, { className: "MJPageBodyComponent", filePath: "lib/page-body/page-body.component.ts", lineNumber: 65 }); })();
89
+ //# sourceMappingURL=page-body.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-body.component.js","sourceRoot":"","sources":["../../../src/lib/page-body/page-body.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;;;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAiCH,MAAM,OAAO,mBAAmB;IAC9B,yEAAyE;IAChE,OAAO,GAAY,IAAI,CAAC;IAEjC;;;;;;OAMG;IACM,IAAI,GAAY,KAAK,CAAC;IAE/B;;;;;OAKG;IACM,SAAS,GAAqB,QAAQ,CAAC;IAEhD,IACI,cAAc;QAChB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,IACI,SAAS;QACX,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,IACI,QAAQ;QACV,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;IAC/C,CAAC;6GAlCU,mBAAmB;6DAAnB,mBAAmB;YAAnB,8DAAmB,qCAAA,mCAAA;;;YA7BnB,kBAAyB;;;iFA6BzB,mBAAmB;cAhC/B,SAAS;2BACE,cAAc,cACZ,IAAI,YACN,2BAA2B;;kBA+BpC,KAAK;;kBASL,KAAK;;kBAQL,KAAK;;kBAEL,WAAW;mBAAC,gCAAgC;;kBAK5C,WAAW;mBAAC,0BAA0B;;kBAKtC,WAAW;mBAAC,yBAAyB;;kFA/B3B,mBAAmB","sourcesContent":["import { Component, HostBinding, Input } from '@angular/core';\n\n/**\n * mj-page-body — Standard body region for MJ Explorer dashboards / resource pages.\n *\n * Sits inside `<mj-page-layout>` after `<mj-page-header>`. Provides the canonical\n * page body recipe: `flex: 1`, `min-height: 0`, padded gutters, vertical scroll.\n *\n * Escape hatches:\n * - `[Padding]=\"false\"` — remove the 24px gutter (e.g., pages whose inner\n * content owns the gutter itself, like File Browser or AI Analytics shell).\n * - `[Flex]=\"true\"` — switch the body to `flex; flex-direction: column;\n * position: relative` so a child marked `flex: 1` (e.g., a main content area\n * that should fill remaining height under a banner row) can grow correctly.\n *\n * Note: overflow-y is intentionally always `auto` and not configurable.\n * `mj-page-layout` already has `overflow: hidden`, so any body content that\n * exceeds the visible area would be silently clipped if the body itself didn't\n * scroll. Pages with split-pane layouts whose children manage their own scroll\n * regions should use `min-height: 0 + flex: 1` on the inner panes so nothing\n * overflows the body — auto stays harmlessly inactive.\n *\n * Example:\n * ```html\n * <mj-page-layout>\n * <mj-page-header Title=\"Jobs\" Icon=\"fa-solid fa-calendar-check\"></mj-page-header>\n * <mj-page-body>\n * <!-- page content -->\n * </mj-page-body>\n * </mj-page-layout>\n * ```\n */\n@Component({\n selector: 'mj-page-body',\n standalone: true,\n template: `<ng-content></ng-content>`,\n styles: [`\n :host {\n display: block;\n flex: 1;\n min-height: 0;\n /* Top padding doubles as header-to-body spacing — <mj-page-header>\n intentionally has no margin-bottom so [Padding]=\"false\" yields a\n truly flush layout (e.g. AI Analytics sidebar+content). */\n padding: var(--mj-space-6);\n overflow-y: auto;\n }\n :host(.mj-page-body--no-padding) {\n padding: 0;\n }\n :host(.mj-page-body--flex) {\n display: flex;\n flex-direction: column;\n position: relative;\n }\n /* Direction=\"row\" — switch flex direction when [Flex]=\"true\". Used by\n shells whose body is a left-rail + content layout (Admin shells, KH\n Config, AI Analytics). Replaces the bespoke per-shell-container body\n grid/flex wrapper that every shell used to declare. */\n :host(.mj-page-body--flex.mj-page-body--row) {\n flex-direction: row;\n }\n `]\n})\nexport class MJPageBodyComponent {\n /** When `false`, removes the default 24px gutter. Defaults to `true`. */\n @Input() Padding: boolean = true;\n\n /**\n * When `true`, switches the body to flex layout (with `position: relative`)\n * so a child marked `flex: 1` can fill the remaining vertical space — e.g., a main\n * content area sitting under a banner row. Defaults to `false` (block flow).\n *\n * Combine with `Direction=\"row\"` to lay out children left-to-right (rail + content).\n */\n @Input() Flex: boolean = false;\n\n /**\n * When `Flex=true`, controls the flex direction. `'column'` (default) stacks\n * children vertically. `'row'` lays them out left-to-right, useful for\n * left-rail + content shells (Admin's `admin-container`, KH Config, etc.).\n * Ignored when `Flex=false`.\n */\n @Input() Direction: 'row' | 'column' = 'column';\n\n @HostBinding('class.mj-page-body--no-padding')\n get NoPaddingClass(): boolean {\n return !this.Padding;\n }\n\n @HostBinding('class.mj-page-body--flex')\n get FlexClass(): boolean {\n return this.Flex;\n }\n\n @HostBinding('class.mj-page-body--row')\n get RowClass(): boolean {\n return this.Flex && this.Direction === 'row';\n }\n}\n"]}