@memberjunction/ng-ui-components 5.37.0 → 5.39.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.
- package/dist/lib/applied-filters/applied-filters.component.d.ts +55 -0
- package/dist/lib/applied-filters/applied-filters.component.d.ts.map +1 -0
- package/dist/lib/applied-filters/applied-filters.component.js +128 -0
- package/dist/lib/applied-filters/applied-filters.component.js.map +1 -0
- package/dist/lib/filter-panel/filter-panel.component.d.ts +14 -0
- package/dist/lib/filter-panel/filter-panel.component.d.ts.map +1 -1
- package/dist/lib/filter-panel/filter-panel.component.js +30 -5
- package/dist/lib/filter-panel/filter-panel.component.js.map +1 -1
- package/dist/lib/filter-popover/filter-popover.component.d.ts +14 -2
- package/dist/lib/filter-popover/filter-popover.component.d.ts.map +1 -1
- package/dist/lib/filter-popover/filter-popover.component.js +153 -52
- package/dist/lib/filter-popover/filter-popover.component.js.map +1 -1
- package/dist/lib/left-nav/left-nav-content.component.d.ts.map +1 -1
- package/dist/lib/left-nav/left-nav-content.component.js +3 -3
- package/dist/lib/left-nav/left-nav-content.component.js.map +1 -1
- package/dist/lib/left-nav/left-nav.component.d.ts +36 -9
- package/dist/lib/left-nav/left-nav.component.d.ts.map +1 -1
- package/dist/lib/left-nav/left-nav.component.js +253 -117
- package/dist/lib/left-nav/left-nav.component.js.map +1 -1
- package/dist/lib/page-body/page-body.component.d.ts.map +1 -1
- package/dist/lib/page-body/page-body.component.js +3 -3
- package/dist/lib/page-body/page-body.component.js.map +1 -1
- package/dist/lib/page-header/page-header.component.js +2 -2
- package/dist/lib/page-header/page-header.scss +51 -4
- package/dist/lib/page-header-interior/page-header-interior.component.d.ts.map +1 -1
- package/dist/lib/page-header-interior/page-header-interior.component.js +3 -3
- package/dist/lib/page-header-interior/page-header-interior.component.js.map +1 -1
- package/dist/lib/refresh-button/refresh-button.component.d.ts.map +1 -1
- package/dist/lib/refresh-button/refresh-button.component.js +13 -10
- package/dist/lib/refresh-button/refresh-button.component.js.map +1 -1
- package/dist/lib/slide-panel/slide-panel.component.d.ts +64 -0
- package/dist/lib/slide-panel/slide-panel.component.d.ts.map +1 -0
- package/dist/lib/slide-panel/slide-panel.component.js +256 -0
- package/dist/lib/slide-panel/slide-panel.component.js.map +1 -0
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +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"]}
|
|
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, HostBinding, HostListener, 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 mobile drawer (≤700px): the rail hides off-canvas and a compact\n * section switcher takes its place; tapping it slides the full rail in over a\n * scrim. The rail keeps its full content (trees, slots, descriptions, badges)\n * inside the drawer — no flattening. Set `[MobileTitle]` for the drawer label.\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 — at ≤700px the rail becomes an off-canvas drawer (see the feature\n * list above). The consumer should still switch the parent's flex-direction from\n * row → column at the same breakpoint (e.g. `<mj-page-body>` Direction=\"row\" with\n * a `@media` override) so the in-flow section switcher stacks above the content\n * pane rather than competing with it for horizontal space.\n */\n@Component({\n selector: 'mj-left-nav',\n standalone: true,\n imports: [CommonModule],\n template: `\n <!-- Mobile-only section switcher (hidden ≥701px via CSS). Shows the active\n section and opens the full rail as an off-canvas drawer on tap. The rail\n itself is too rich (trees, header/footer slots, descriptions, badges) to\n flatten into a dropdown or tab strip, so we surface it as-is in a drawer. -->\n <button\n type=\"button\"\n class=\"mj-left-nav__switcher\"\n [attr.aria-expanded]=\"MobileNavOpen\"\n aria-haspopup=\"dialog\"\n (click)=\"OpenMobileNav()\">\n @if (ActiveTriggerItem?.icon) {\n <i class=\"mj-left-nav__switcher-icon\" [class]=\"ActiveTriggerItem!.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"mj-left-nav__switcher-label\">{{ ActiveTriggerItem?.label || MobileTitle }}</span>\n <i class=\"fa-solid fa-bars-staggered mj-left-nav__switcher-hint\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- Scrim behind the mobile drawer (hidden ≥701px / when closed via CSS). -->\n <div class=\"mj-left-nav__scrim\" (click)=\"CloseMobileNav()\" aria-hidden=\"true\"></div>\n\n <aside\n class=\"mj-left-nav\"\n [style.width.px]=\"Width\"\n role=\"navigation\"\n [attr.aria-modal]=\"MobileNavOpen ? true : null\">\n <!-- Drawer header — only visible on mobile when the rail is a drawer. -->\n <div class=\"mj-left-nav__drawer-header\">\n <span class=\"mj-left-nav__drawer-title\">{{ MobileTitle }}</span>\n <button\n type=\"button\"\n class=\"mj-left-nav__drawer-close\"\n aria-label=\"Close menu\"\n (click)=\"CloseMobileNav()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\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 /* ── Mobile drawer pattern (≤700px) ──────────────────────────────────────\n Desktop shows the rail inline. On mobile the rail would otherwise eat the\n top of the screen, so we hide it off-canvas and surface a compact section\n switcher in its place. Tapping the switcher slides the full rail in from\n the left over a scrim — preserving trees, header/footer slots and badges\n (item descriptions hide in the drawer for a tighter list).\n\n The switcher / scrim / drawer-header are display:none above the\n breakpoint; the rail keeps its desktop layout there. */\n .mj-left-nav__switcher,\n .mj-left-nav__scrim,\n .mj-left-nav__drawer-header {\n display: none;\n }\n\n @media (max-width: 700px) {\n /* Host collapses to the switcher's footprint; the rail goes out of flow. */\n :host {\n width: 100% !important;\n }\n\n /* Hide item descriptions in the drawer — the label + icon are enough on a\n narrow sheet; the secondary line just adds clutter. */\n .mj-left-nav__description {\n display: none;\n }\n\n /* Section switcher — reads as a picker for the current section. */\n .mj-left-nav__switcher {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 11px 14px;\n border: none;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n text-align: left;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n }\n .mj-left-nav__switcher:active {\n background: var(--mj-bg-surface-active);\n }\n .mj-left-nav__switcher-icon {\n width: 18px;\n text-align: center;\n font-size: 14px;\n flex-shrink: 0;\n color: var(--mj-brand-primary);\n }\n .mj-left-nav__switcher-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .mj-left-nav__switcher-hint {\n flex-shrink: 0;\n font-size: 15px;\n color: var(--mj-text-muted);\n }\n\n /* Scrim — fades in with the drawer. */\n .mj-left-nav__scrim {\n display: block;\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.24s ease;\n z-index: 9998;\n }\n :host(.mj-left-nav-host--open) .mj-left-nav__scrim {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* Rail becomes a left off-canvas drawer. */\n .mj-left-nav {\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: min(320px, 84vw) !important;\n height: 100%;\n max-height: none;\n border-right: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-lg);\n transform: translateX(-100%);\n transition: transform 0.24s ease;\n z-index: 9999;\n padding-top: 0;\n }\n :host(.mj-left-nav-host--open) .mj-left-nav {\n transform: translateX(0);\n }\n\n /* Drawer header — sticky title + close button. */\n .mj-left-nav__drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n position: sticky;\n top: 0;\n z-index: 1;\n padding: 14px 12px 10px;\n margin: 0 -8px 4px;\n padding-left: 16px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .mj-left-nav__drawer-title {\n font-size: 13px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n }\n .mj-left-nav__drawer-close {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 16px;\n font-family: inherit;\n }\n .mj-left-nav__drawer-close:active {\n background: var(--mj-bg-surface-active);\n }\n }\n\n @media (max-width: 700px) and (prefers-reduced-motion: reduce) {\n .mj-left-nav,\n .mj-left-nav__scrim {\n transition: none;\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. Only applies to the desktop rail — on mobile\n * the rail becomes a fixed-width off-canvas drawer.\n */\n @Input() Width: number = 240;\n\n /**\n * Label shown on the mobile drawer header and used as the switcher fallback\n * label when no item is active. Defaults to `'Menu'`. Set to something\n * domain-specific (e.g. `'Sections'`, `'Test Suites'`) for clearer context.\n */\n @Input() MobileTitle: string = 'Menu';\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 /**\n * Whether the mobile off-canvas drawer is open. Component-owned, transient UI\n * state — no effect above the 700px breakpoint where the rail is always inline.\n */\n public MobileNavOpen = false;\n\n /** Reflects {@link MobileNavOpen} onto the host so CSS can drive the drawer. */\n @HostBinding('class.mj-left-nav-host--open')\n get isMobileNavOpen(): boolean {\n return this.MobileNavOpen;\n }\n\n OpenMobileNav(): void {\n this.MobileNavOpen = true;\n }\n\n CloseMobileNav(): void {\n this.MobileNavOpen = false;\n }\n\n /** Escape closes the mobile drawer. No-op when it's already closed. */\n @HostListener('document:keydown.escape')\n onEscape(): void {\n if (this.MobileNavOpen) {\n this.MobileNavOpen = false;\n }\n }\n\n OnItemClick(item: MJLeftNavItem): void {\n if (item.disabled) return;\n // Selecting a leaf dismisses the mobile drawer; harmless on desktop.\n this.MobileNavOpen = false;\n this.ItemClicked.emit(item);\n }\n\n OnItemToggle(event: Event, item: MJLeftNavItem): void {\n event.stopPropagation();\n // Expanding/collapsing a tree branch keeps the drawer open.\n this.ItemToggled.emit(item);\n }\n\n /**\n * The item matching {@link ActiveId} (searched recursively through trees),\n * used to label the mobile section switcher with the current section's icon\n * and label. Returns `undefined` when nothing is active.\n */\n get ActiveTriggerItem(): MJLeftNavItem | undefined {\n if (!this.ActiveId) return undefined;\n return this.findItemById(this.Sections.flatMap((s) => s.items), this.ActiveId);\n }\n\n private findItemById(items: MJLeftNavItem[], id: string): MJLeftNavItem | undefined {\n for (const item of items) {\n if (item.id === id) return item;\n if (item.children?.length) {\n const found = this.findItemById(item.children, id);\n if (found) return found;\n }\n }\n return undefined;\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"]}
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"page-body.component.d.ts","sourceRoot":"","sources":["../../../src/lib/page-body/page-body.component.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAyCa,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"}
|
|
@@ -64,11 +64,11 @@ export class MJPageBodyComponent {
|
|
|
64
64
|
} }, inputs: { Padding: "Padding", Flex: "Flex", Direction: "Direction" }, ngContentSelectors: _c0, decls: 1, vars: 0, template: function MJPageBodyComponent_Template(rf, ctx) { if (rf & 1) {
|
|
65
65
|
i0.ɵɵprojectionDef();
|
|
66
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 }"] });
|
|
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 }\n \n\n\n\n\n @media (max-width: 700px) {\n .mj-page-body--flex.mj-page-body--row[_nghost-%COMP%] {\n flex-direction: column;\n }\n }"] });
|
|
68
68
|
}
|
|
69
69
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJPageBodyComponent, [{
|
|
70
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 "] }]
|
|
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 /* On mobile a left-rail + content row can't coexist horizontally \u2014 the rail\n (mj-left-nav) collapses to a full-width drawer trigger at \u2264700px, so the\n row must stack: the trigger bar on top, the content pane filling the rest.\n Pairs with mj-left-nav-content's min-height:0 so the content still scrolls. */\n @media (max-width: 700px) {\n :host(.mj-page-body--flex.mj-page-body--row) {\n flex-direction: column;\n }\n }\n "] }]
|
|
72
72
|
}], null, { Padding: [{
|
|
73
73
|
type: Input
|
|
74
74
|
}], Flex: [{
|
|
@@ -85,5 +85,5 @@ export class MJPageBodyComponent {
|
|
|
85
85
|
type: HostBinding,
|
|
86
86
|
args: ['class.mj-page-body--row']
|
|
87
87
|
}] }); })();
|
|
88
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJPageBodyComponent, { className: "MJPageBodyComponent", filePath: "lib/page-body/page-body.component.ts", lineNumber:
|
|
88
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJPageBodyComponent, { className: "MJPageBodyComponent", filePath: "lib/page-body/page-body.component.ts", lineNumber: 74 }); })();
|
|
89
89
|
//# sourceMappingURL=page-body.component.js.map
|
|
@@ -1 +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;
|
|
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;AA0CH,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;;;YAtCnB,kBAAyB;;;iFAsCzB,mBAAmB;cAzC/B,SAAS;2BACE,cAAc,cACZ,IAAI,YACN,2BAA2B;;kBAwCpC,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 /* On mobile a left-rail + content row can't coexist horizontally — the rail\n (mj-left-nav) collapses to a full-width drawer trigger at ≤700px, so the\n row must stack: the trigger bar on top, the content pane filling the rest.\n Pairs with mj-left-nav-content's min-height:0 so the content still scrolls. */\n @media (max-width: 700px) {\n :host(.mj-page-body--flex.mj-page-body--row) {\n flex-direction: column;\n }\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"]}
|
|
@@ -83,7 +83,7 @@ export class MJPageHeaderComponent {
|
|
|
83
83
|
i0.ɵɵtextInterpolate(ctx.Title);
|
|
84
84
|
i0.ɵɵadvance();
|
|
85
85
|
i0.ɵɵconditional(ctx.Subtitle ? 7 : -1);
|
|
86
|
-
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n \n\n\n\n margin-bottom: var(--mj-space-6);\n}\n\n\n\n\n\n\nmj-page-layout[_nghost-%COMP%], mj-page-layout [_nghost-%COMP%] {\n margin-bottom: 0;\n}\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] [meta], \n[_nghost-%COMP%] [actions], \n[_nghost-%COMP%] [toolbar] {\n display: contents;\n}\n\n.mj-page-header[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n \n\n\n\n\n\n\n box-shadow: var(--mj-shadow-md);\n position: relative;\n z-index: 1;\n}\n\n.mj-page-header-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: 0 var(--mj-space-6);\n}\n\n\n\n\n\n\n\n\n.mj-page-header-row--primary[_ngcontent-%COMP%] {\n justify-content: space-between;\n align-items: center;\n gap: var(--mj-space-4);\n padding-top: var(--mj-space-4);\n padding-bottom: var(--mj-space-4);\n}\n\n.mj-page-header-row--toolbar[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n padding-top: 0;\n padding-bottom: var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 0;\n padding-top: var(--mj-space-3);\n}\n\n.mj-page-header-row--toolbar[_ngcontent-%COMP%]:empty {\n display: none;\n border-top: 0;\n padding: 0;\n}\n\n.mj-page-header-titlebar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mj-page-header-icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-lg);\n}\n\n\n\n.mj-page-header-text[_ngcontent-%COMP%] {\n min-width: 0;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n}\n\n.mj-page-header-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mj-page-header-subtitle[_ngcontent-%COMP%] {\n margin: var(--mj-space-1) 0 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n\n\n\n\n\n.mj-page-header-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n}\n\n.mj-page-header-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n}\n\n.mj-page-header-meta[_ngcontent-%COMP%]:empty, \n.mj-page-header-actions[_ngcontent-%COMP%]:empty {\n display: none;\n}\n\n@media (max-width: 768px) {\n .mj-page-header-row--primary[_ngcontent-%COMP%] {\n flex-
|
|
86
|
+
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n \n\n\n\n margin-bottom: var(--mj-space-6);\n}\n\n\n\n\n\n\nmj-page-layout[_nghost-%COMP%], mj-page-layout [_nghost-%COMP%] {\n margin-bottom: 0;\n}\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] [meta], \n[_nghost-%COMP%] [actions], \n[_nghost-%COMP%] [toolbar] {\n display: contents;\n}\n\n.mj-page-header[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n \n\n\n\n\n\n\n box-shadow: var(--mj-shadow-md);\n position: relative;\n z-index: 1;\n}\n\n.mj-page-header-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: 0 var(--mj-space-6);\n}\n\n\n\n\n\n\n\n\n.mj-page-header-row--primary[_ngcontent-%COMP%] {\n justify-content: space-between;\n align-items: center;\n gap: var(--mj-space-4);\n padding-top: var(--mj-space-4);\n padding-bottom: var(--mj-space-4);\n}\n\n.mj-page-header-row--toolbar[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n \n\n\n justify-content: flex-end;\n padding-top: 0;\n padding-bottom: var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 0;\n padding-top: var(--mj-space-3);\n}\n\n.mj-page-header-row--toolbar[_ngcontent-%COMP%]:empty {\n display: none;\n border-top: 0;\n padding: 0;\n}\n\n.mj-page-header-titlebar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mj-page-header-icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-lg);\n}\n\n\n\n.mj-page-header-text[_ngcontent-%COMP%] {\n min-width: 0;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n}\n\n.mj-page-header-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mj-page-header-subtitle[_ngcontent-%COMP%] {\n margin: var(--mj-space-1) 0 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n\n\n\n\n\n.mj-page-header-meta[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n}\n\n.mj-page-header-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n}\n\n.mj-page-header-meta[_ngcontent-%COMP%]:empty, \n.mj-page-header-actions[_ngcontent-%COMP%]:empty {\n display: none;\n}\n\n\n\n\n\n\n\n@media (max-width: 768px) {\n .mj-page-header-row[_ngcontent-%COMP%] {\n padding-left: var(--mj-space-4);\n padding-right: var(--mj-space-4);\n gap: var(--mj-space-2);\n }\n .mj-page-header-row--primary[_ngcontent-%COMP%] {\n \n\n\n\n gap: var(--mj-space-2);\n padding-top: var(--mj-space-3);\n padding-bottom: var(--mj-space-3);\n }\n \n\n .mj-page-header-icon[_ngcontent-%COMP%], \n .mj-page-header-subtitle[_ngcontent-%COMP%] {\n display: none;\n }\n .mj-page-header-title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-lg);\n line-height: 1.2;\n }\n .mj-page-header-meta[_ngcontent-%COMP%] {\n margin-top: var(--mj-space-1);\n }\n .mj-page-header-actions[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--mj-space-2);\n }\n \n\n\n .mj-page-header-actions[_ngcontent-%COMP%] .action-btn-label {\n display: none;\n }\n .mj-page-header-row--toolbar[_ngcontent-%COMP%] {\n \n\n justify-content: flex-start;\n padding-top: var(--mj-space-2);\n padding-bottom: var(--mj-space-3);\n }\n \n\n\n .mj-page-header-row--toolbar[_ngcontent-%COMP%] mj-page-search {\n flex: 1;\n min-width: 0;\n }\n}"] });
|
|
87
87
|
}
|
|
88
88
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJPageHeaderComponent, [{
|
|
89
89
|
type: Component,
|
|
@@ -114,7 +114,7 @@ export class MJPageHeaderComponent {
|
|
|
114
114
|
<ng-content select="[toolbar]"></ng-content>
|
|
115
115
|
</div>
|
|
116
116
|
</header>
|
|
117
|
-
`, styles: [":host {\n display: block;\n /* External consumers using <mj-page-header> outside <mj-page-layout> get a\n 24px bottom margin so the header has breathing room from whatever follows.\n This preserves backward compatibility with the published v5.x contract. */\n margin-bottom: var(--mj-space-6);\n}\n\n/* When nested inside <mj-page-layout>, the spacing between header and body is\n owned by <mj-page-body>'s padding-top \u2014 so the header sits flush against the\n body. This lets pages using <mj-page-body [Padding]=\"false\"> (e.g. AI\n Analytics flush sidebar) render truly flush against the header. */\n:host-context(mj-page-layout) {\n margin-bottom: 0;\n}\n\n/* Slot-wrapper passthrough \u2014 any element a consumer projects with the `meta`,\n `actions`, or `toolbar` attribute (regardless of class name) becomes a\n transparent container so its children become direct flex children of the\n page-header row and inherit the row's gap. Eliminates the per-page\n `.{section}-header-meta { display: contents }` rules that pages had to\n declare in their own CSS files. */\n:host ::ng-deep [meta],\n:host ::ng-deep [actions],\n:host ::ng-deep [toolbar] {\n display: contents;\n}\n\n.mj-page-header {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n /* Drop shadow for visual separation from the body below.\n `position: relative; z-index: 1` lifts the header into its own stacking\n context above <mj-page-body> (which has `position: relative` and would\n otherwise paint over the downward shadow as a later flex sibling).\n --mj-shadow-md is used instead of --mj-shadow-sm because the latter is\n rgba(0,0,0,0.05) in light mode \u2014 too faint to read as separation. */\n box-shadow: var(--mj-shadow-md);\n position: relative;\n z-index: 1;\n}\n\n.mj-page-header-row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: 0 var(--mj-space-6);\n}\n\n/* Primary row centers the actions cluster vertically against the identity\n column. The identity column stacks title \u2192 subtitle \u2192 (optional meta);\n centering keeps the actions visually balanced against the whole block\n instead of pinning them to the title baseline. When meta badges are\n present the actions drift down a touch \u2014 acceptable trade for the more\n common no-meta case where flex-start looks top-heavy. */\n.mj-page-header-row--primary {\n justify-content: space-between;\n align-items: center;\n gap: var(--mj-space-4);\n padding-top: var(--mj-space-4);\n padding-bottom: var(--mj-space-4);\n}\n\n.mj-page-header-row--toolbar {\n flex-wrap: wrap;\n padding-top: 0;\n padding-bottom: var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 0;\n padding-top: var(--mj-space-3);\n}\n\n.mj-page-header-row--toolbar:empty {\n display: none;\n border-top: 0;\n padding: 0;\n}\n\n.mj-page-header-titlebar {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mj-page-header-icon {\n flex-shrink: 0;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-lg);\n}\n\n/* Identity column \u2014 title, subtitle, meta stacked vertically. */\n.mj-page-header-text {\n min-width: 0;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n}\n\n.mj-page-header-title {\n margin: 0;\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mj-page-header-subtitle {\n margin: var(--mj-space-1) 0 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n/* Meta lives in the identity column UNDER the subtitle \u2014 supporting info\n for the identity, not a peer to the title. Wraps when there are many\n badges. Matches the mj-page-header-interior pattern so both chrome\n surfaces have one mental model. */\n.mj-page-header-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n}\n\n.mj-page-header-actions {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n}\n\n.mj-page-header-meta:empty,\n.mj-page-header-actions:empty {\n display: none;\n}\n\n@media (max-width: 768px) {\n .mj-page-header-row--primary {\n flex-
|
|
117
|
+
`, styles: [":host {\n display: block;\n /* External consumers using <mj-page-header> outside <mj-page-layout> get a\n 24px bottom margin so the header has breathing room from whatever follows.\n This preserves backward compatibility with the published v5.x contract. */\n margin-bottom: var(--mj-space-6);\n}\n\n/* When nested inside <mj-page-layout>, the spacing between header and body is\n owned by <mj-page-body>'s padding-top \u2014 so the header sits flush against the\n body. This lets pages using <mj-page-body [Padding]=\"false\"> (e.g. AI\n Analytics flush sidebar) render truly flush against the header. */\n:host-context(mj-page-layout) {\n margin-bottom: 0;\n}\n\n/* Slot-wrapper passthrough \u2014 any element a consumer projects with the `meta`,\n `actions`, or `toolbar` attribute (regardless of class name) becomes a\n transparent container so its children become direct flex children of the\n page-header row and inherit the row's gap. Eliminates the per-page\n `.{section}-header-meta { display: contents }` rules that pages had to\n declare in their own CSS files. */\n:host ::ng-deep [meta],\n:host ::ng-deep [actions],\n:host ::ng-deep [toolbar] {\n display: contents;\n}\n\n.mj-page-header {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n /* Drop shadow for visual separation from the body below.\n `position: relative; z-index: 1` lifts the header into its own stacking\n context above <mj-page-body> (which has `position: relative` and would\n otherwise paint over the downward shadow as a later flex sibling).\n --mj-shadow-md is used instead of --mj-shadow-sm because the latter is\n rgba(0,0,0,0.05) in light mode \u2014 too faint to read as separation. */\n box-shadow: var(--mj-shadow-md);\n position: relative;\n z-index: 1;\n}\n\n.mj-page-header-row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: 0 var(--mj-space-6);\n}\n\n/* Primary row centers the actions cluster vertically against the identity\n column. The identity column stacks title \u2192 subtitle \u2192 (optional meta);\n centering keeps the actions visually balanced against the whole block\n instead of pinning them to the title baseline. When meta badges are\n present the actions drift down a touch \u2014 acceptable trade for the more\n common no-meta case where flex-start looks top-heavy. */\n.mj-page-header-row--primary {\n justify-content: space-between;\n align-items: center;\n gap: var(--mj-space-4);\n padding-top: var(--mj-space-4);\n padding-bottom: var(--mj-space-4);\n}\n\n.mj-page-header-row--toolbar {\n flex-wrap: wrap;\n /* Control bar (search \u00B7 Filter \u00B7 view) is right-aligned on desktop; the\n mobile block below resets it to flex-start. */\n justify-content: flex-end;\n padding-top: 0;\n padding-bottom: var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n margin-top: 0;\n padding-top: var(--mj-space-3);\n}\n\n.mj-page-header-row--toolbar:empty {\n display: none;\n border-top: 0;\n padding: 0;\n}\n\n.mj-page-header-titlebar {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.mj-page-header-icon {\n flex-shrink: 0;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n color: var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-lg);\n}\n\n/* Identity column \u2014 title, subtitle, meta stacked vertically. */\n.mj-page-header-text {\n min-width: 0;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n}\n\n.mj-page-header-title {\n margin: 0;\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mj-page-header-subtitle {\n margin: var(--mj-space-1) 0 0;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n/* Meta lives in the identity column UNDER the subtitle \u2014 supporting info\n for the identity, not a peer to the title. Wraps when there are many\n badges. Matches the mj-page-header-interior pattern so both chrome\n surfaces have one mental model. */\n.mj-page-header-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n}\n\n.mj-page-header-actions {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n}\n\n.mj-page-header-meta:empty,\n.mj-page-header-actions:empty {\n display: none;\n}\n\n/* \u2500\u2500 Mobile compaction (\u2264768px) \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 On a phone the header must not eat the viewport. We tighten padding, drop\n the title a size, hide the decorative icon + subtitle (identity is carried\n by the title alone), and let the actions cluster wrap tightly under the\n title. The meta badge and toolbar stay \u2014 they're functional. */\n@media (max-width: 768px) {\n .mj-page-header-row {\n padding-left: var(--mj-space-4);\n padding-right: var(--mj-space-4);\n gap: var(--mj-space-2);\n }\n .mj-page-header-row--primary {\n /* Stays a single row on mobile: title (truncates) + the primary CTA in\n [actions]. Filters/search/view now live in the [toolbar] control bar, so\n the primary row no longer needs to stack. */\n gap: var(--mj-space-2);\n padding-top: var(--mj-space-3);\n padding-bottom: var(--mj-space-3);\n }\n /* Icon + subtitle are desktop niceties; reclaim their footprint on mobile. */\n .mj-page-header-icon,\n .mj-page-header-subtitle {\n display: none;\n }\n .mj-page-header-title {\n font-size: var(--mj-text-lg);\n line-height: 1.2;\n }\n .mj-page-header-meta {\n margin-top: var(--mj-space-1);\n }\n .mj-page-header-actions {\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--mj-space-2);\n }\n /* Action buttons go icon-only on mobile \u2014 consumers wrap the text in\n <span class=\"action-btn-label\"> so it can hide without touching mjButton. */\n .mj-page-header-actions ::ng-deep .action-btn-label {\n display: none;\n }\n .mj-page-header-row--toolbar {\n /* Left-aligned on mobile (search grows to fill the row). */\n justify-content: flex-start;\n padding-top: var(--mj-space-2);\n padding-bottom: var(--mj-space-3);\n }\n /* In the control bar (search \u00B7 Filter \u00B7 view), let search grow so the Filter\n button + view-toggle sit at the trailing edge on one line. */\n .mj-page-header-row--toolbar ::ng-deep mj-page-search {\n flex: 1;\n min-width: 0;\n }\n}\n"] }]
|
|
118
118
|
}], null, { Title: [{
|
|
119
119
|
type: Input
|
|
120
120
|
}], Icon: [{
|
|
@@ -64,6 +64,9 @@
|
|
|
64
64
|
|
|
65
65
|
.mj-page-header-row--toolbar {
|
|
66
66
|
flex-wrap: wrap;
|
|
67
|
+
/* Control bar (search · Filter · view) is right-aligned on desktop; the
|
|
68
|
+
mobile block below resets it to flex-start. */
|
|
69
|
+
justify-content: flex-end;
|
|
67
70
|
padding-top: 0;
|
|
68
71
|
padding-bottom: var(--mj-space-4);
|
|
69
72
|
border-top: 1px solid var(--mj-border-subtle);
|
|
@@ -148,13 +151,57 @@
|
|
|
148
151
|
display: none;
|
|
149
152
|
}
|
|
150
153
|
|
|
154
|
+
/* ── Mobile compaction (≤768px) ───────────────────────────────────────────
|
|
155
|
+
On a phone the header must not eat the viewport. We tighten padding, drop
|
|
156
|
+
the title a size, hide the decorative icon + subtitle (identity is carried
|
|
157
|
+
by the title alone), and let the actions cluster wrap tightly under the
|
|
158
|
+
title. The meta badge and toolbar stay — they're functional. */
|
|
151
159
|
@media (max-width: 768px) {
|
|
160
|
+
.mj-page-header-row {
|
|
161
|
+
padding-left: var(--mj-space-4);
|
|
162
|
+
padding-right: var(--mj-space-4);
|
|
163
|
+
gap: var(--mj-space-2);
|
|
164
|
+
}
|
|
152
165
|
.mj-page-header-row--primary {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
/* Stays a single row on mobile: title (truncates) + the primary CTA in
|
|
167
|
+
[actions]. Filters/search/view now live in the [toolbar] control bar, so
|
|
168
|
+
the primary row no longer needs to stack. */
|
|
169
|
+
gap: var(--mj-space-2);
|
|
170
|
+
padding-top: var(--mj-space-3);
|
|
171
|
+
padding-bottom: var(--mj-space-3);
|
|
172
|
+
}
|
|
173
|
+
/* Icon + subtitle are desktop niceties; reclaim their footprint on mobile. */
|
|
174
|
+
.mj-page-header-icon,
|
|
175
|
+
.mj-page-header-subtitle {
|
|
176
|
+
display: none;
|
|
177
|
+
}
|
|
178
|
+
.mj-page-header-title {
|
|
179
|
+
font-size: var(--mj-text-lg);
|
|
180
|
+
line-height: 1.2;
|
|
181
|
+
}
|
|
182
|
+
.mj-page-header-meta {
|
|
183
|
+
margin-top: var(--mj-space-1);
|
|
156
184
|
}
|
|
157
185
|
.mj-page-header-actions {
|
|
158
|
-
|
|
186
|
+
flex-wrap: wrap;
|
|
187
|
+
justify-content: flex-start;
|
|
188
|
+
gap: var(--mj-space-2);
|
|
189
|
+
}
|
|
190
|
+
/* Action buttons go icon-only on mobile — consumers wrap the text in
|
|
191
|
+
<span class="action-btn-label"> so it can hide without touching mjButton. */
|
|
192
|
+
.mj-page-header-actions ::ng-deep .action-btn-label {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
195
|
+
.mj-page-header-row--toolbar {
|
|
196
|
+
/* Left-aligned on mobile (search grows to fill the row). */
|
|
197
|
+
justify-content: flex-start;
|
|
198
|
+
padding-top: var(--mj-space-2);
|
|
199
|
+
padding-bottom: var(--mj-space-3);
|
|
200
|
+
}
|
|
201
|
+
/* In the control bar (search · Filter · view), let search grow so the Filter
|
|
202
|
+
button + view-toggle sit at the trailing edge on one line. */
|
|
203
|
+
.mj-page-header-row--toolbar ::ng-deep mj-page-search {
|
|
204
|
+
flex: 1;
|
|
205
|
+
min-width: 0;
|
|
159
206
|
}
|
|
160
207
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-header-interior.component.d.ts","sourceRoot":"","sources":["../../../src/lib/page-header-interior/page-header-interior.component.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,
|
|
1
|
+
{"version":3,"file":"page-header-interior.component.d.ts","sourceRoot":"","sources":["../../../src/lib/page-header-interior/page-header-interior.component.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,qBA+Ma,6BAA6B;IACxC;;;;OAIG;IACM,IAAI,EAAE,MAAM,GAAG,IAAI,CAAY;IAExC;;;;OAIG;IACM,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzC;;;;OAIG;IACM,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC;;;;;OAKG;IACM,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;yCA5B7B,6BAA6B;2CAA7B,6BAA6B;CA6BzC"}
|
|
@@ -133,7 +133,7 @@ export class MJPageHeaderInteriorComponent {
|
|
|
133
133
|
i0.ɵɵconditional(ctx.Title ? 3 : -1);
|
|
134
134
|
i0.ɵɵadvance();
|
|
135
135
|
i0.ɵɵconditional(ctx.Subtitle ? 4 : -1);
|
|
136
|
-
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n \n\n\n\n\n\n [_nghost-%COMP%] [meta], \n [_nghost-%COMP%] [actions], \n [_nghost-%COMP%] [toolbar] {\n display: contents;\n }\n\n\n \n\n\n\n\n\n .mj-page-header-interior__row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary[_ngcontent-%COMP%] {\n gap: var(--mj-space-4);\n }\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n .mj-page-header-interior__row--toolbar[_ngcontent-%COMP%] {\n display: none;\n }\n\n \n\n\n\n\n [_nghost-%COMP%] .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n \n\n\n\n .mj-page-header-interior__title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n \n\n\n\n\n\n\n .mj-page-header-interior__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n \n\n .mj-page-header-interior__meta[_ngcontent-%COMP%]:empty, \n .mj-page-header-interior__actions[_ngcontent-%COMP%]:empty {\n display: none;\n }\n\n \n\n\n\n .mj-page-header-interior__identity[_ngcontent-%COMP%]:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n \n\n\n"] });
|
|
136
|
+
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n \n\n\n\n\n\n [_nghost-%COMP%] [meta], \n [_nghost-%COMP%] [actions], \n [_nghost-%COMP%] [toolbar] {\n display: contents;\n }\n\n\n \n\n\n\n\n\n .mj-page-header-interior__row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary[_ngcontent-%COMP%] {\n gap: var(--mj-space-4);\n }\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n .mj-page-header-interior__row--toolbar[_ngcontent-%COMP%] {\n display: none;\n }\n\n \n\n\n\n\n [_nghost-%COMP%] .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n \n\n justify-content: flex-end;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n \n\n\n\n .mj-page-header-interior__title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n \n\n\n\n\n\n\n .mj-page-header-interior__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n \n\n .mj-page-header-interior__meta[_ngcontent-%COMP%]:empty, \n .mj-page-header-interior__actions[_ngcontent-%COMP%]:empty {\n display: none;\n }\n\n \n\n\n\n .mj-page-header-interior__identity[_ngcontent-%COMP%]:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n \n\n\n\n\n \n\n\n\n\n\n @media (max-width: 768px) {\n [_nghost-%COMP%] {\n margin: 0;\n }\n .mj-page-header-interior__title[_ngcontent-%COMP%], \n .mj-page-header-interior__subtitle[_ngcontent-%COMP%] {\n display: none;\n }\n \n\n\n\n [_nghost-%COMP%] .mj-page-header-interior__actions .mj-action-label {\n display: none;\n }\n \n\n\n [_nghost-%COMP%] .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n flex-wrap: nowrap;\n overflow-x: auto;\n \n\n justify-content: flex-start;\n }\n \n\n [_nghost-%COMP%] .mj-page-header-interior__row--toolbar mj-page-search {\n flex: 1;\n min-width: 0;\n }\n }"] });
|
|
137
137
|
}
|
|
138
138
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJPageHeaderInteriorComponent, [{
|
|
139
139
|
type: Component,
|
|
@@ -162,7 +162,7 @@ export class MJPageHeaderInteriorComponent {
|
|
|
162
162
|
<ng-content select="[toolbar]"></ng-content>
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
|
-
`, styles: ["\n :host {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n /* Slot-wrapper passthrough \u2014 any element a consumer projects with the\n `meta`, `actions`, or `toolbar` attribute (regardless of class name)\n becomes a transparent container so its children become direct flex\n children of the row wrapper and inherit the row's gap. Matches the\n same pattern <mj-page-header> uses. */\n :host ::ng-deep [meta],\n :host ::ng-deep [actions],\n :host ::ng-deep [toolbar] {\n display: contents;\n }\n\n\n /* Rows: primary (identity + actions) and toolbar (separate row).\n Primary row centers actions vertically against the identity column\n (title \u2192 subtitle \u2192 optional meta). When meta is present the actions\n drift down slightly \u2014 acceptable trade for the more common no-meta\n case where flex-start looks top-heavy. */\n .mj-page-header-interior__row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary {\n gap: var(--mj-space-4);\n }\n\n /* Toolbar row \u2014 hidden by default, opt-in via consumer projection.\n\n Defaulting to display:none (instead of \"show then hide when empty\")\n eliminates the dangling divider + empty band on sub-pages that have\n no toolbar content (Dev Tools inspectors, action-only sections, etc.).\n The row only appears when the consumer projects a [toolbar] element\n that actually has content.\n\n ::ng-deep is required because the projected [toolbar] wrapper carries\n the CONSUMER's view-encapsulation scope, not ours \u2014 a scoped selector\n for [toolbar] would never match the projected wrapper, so we have to\n escape our scope to see it. The matched-content rule restores all\n the row's intended styling (flex layout, padding, divider). */\n .mj-page-header-interior__row--toolbar {\n display: none;\n }\n\n /* Nested :has(... :has(...)) is disallowed by browsers (Chromium throws\n SyntaxError; the rule is silently dropped). Use a single-level :has\n that walks two generations directly: \"toolbar row containing a [toolbar]\n wrapper that itself has any element child\". */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n /* Identity typography matches <mj-page-header> exactly \u2014 same title size,\n same subtitle size, same colors. One mental model across both chrome\n surfaces. */\n .mj-page-header-interior__title {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* Meta lives in the identity column UNDER the subtitle \u2014 it's supporting\n info for the identity, not a peer to the title. Wraps when there are\n many badges so it doesn't force horizontal overflow. The top margin\n gives the badge row clear separation from the subtitle so it reads as\n a distinct band of supporting data rather than a continuation of the\n prose line above. */\n .mj-page-header-interior__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n /* Collapse the meta + actions wrappers when there's nothing projected. */\n .mj-page-header-interior__meta:empty,\n .mj-page-header-interior__actions:empty {\n display: none;\n }\n\n /* Collapse the identity column entirely when title/subtitle aren't set AND\n no [meta] content is projected \u2014 covers the action-band-only sub-pages\n (Dev Tools inspectors, SQL Logging) where the card is pure actions. */\n .mj-page-header-interior__identity:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n /* No .mj-btn reset here. Buttons projected into the chrome inherit the\n mjButton directive's global styles from button.scss \u2014 see \"Button Styling\"\n in packages/Angular/CLAUDE.md. */\n "] }]
|
|
165
|
+
`, styles: ["\n :host {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n /* Slot-wrapper passthrough \u2014 any element a consumer projects with the\n `meta`, `actions`, or `toolbar` attribute (regardless of class name)\n becomes a transparent container so its children become direct flex\n children of the row wrapper and inherit the row's gap. Matches the\n same pattern <mj-page-header> uses. */\n :host ::ng-deep [meta],\n :host ::ng-deep [actions],\n :host ::ng-deep [toolbar] {\n display: contents;\n }\n\n\n /* Rows: primary (identity + actions) and toolbar (separate row).\n Primary row centers actions vertically against the identity column\n (title \u2192 subtitle \u2192 optional meta). When meta is present the actions\n drift down slightly \u2014 acceptable trade for the more common no-meta\n case where flex-start looks top-heavy. */\n .mj-page-header-interior__row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary {\n gap: var(--mj-space-4);\n }\n\n /* Toolbar row \u2014 hidden by default, opt-in via consumer projection.\n\n Defaulting to display:none (instead of \"show then hide when empty\")\n eliminates the dangling divider + empty band on sub-pages that have\n no toolbar content (Dev Tools inspectors, action-only sections, etc.).\n The row only appears when the consumer projects a [toolbar] element\n that actually has content.\n\n ::ng-deep is required because the projected [toolbar] wrapper carries\n the CONSUMER's view-encapsulation scope, not ours \u2014 a scoped selector\n for [toolbar] would never match the projected wrapper, so we have to\n escape our scope to see it. The matched-content rule restores all\n the row's intended styling (flex layout, padding, divider). */\n .mj-page-header-interior__row--toolbar {\n display: none;\n }\n\n /* Nested :has(... :has(...)) is disallowed by browsers (Chromium throws\n SyntaxError; the rule is silently dropped). Use a single-level :has\n that walks two generations directly: \"toolbar row containing a [toolbar]\n wrapper that itself has any element child\". */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n /* Right-aligned control bar on desktop; mobile block resets to flex-start. */\n justify-content: flex-end;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n /* Identity typography matches <mj-page-header> exactly \u2014 same title size,\n same subtitle size, same colors. One mental model across both chrome\n surfaces. */\n .mj-page-header-interior__title {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* Meta lives in the identity column UNDER the subtitle \u2014 it's supporting\n info for the identity, not a peer to the title. Wraps when there are\n many badges so it doesn't force horizontal overflow. The top margin\n gives the badge row clear separation from the subtitle so it reads as\n a distinct band of supporting data rather than a continuation of the\n prose line above. */\n .mj-page-header-interior__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n /* Collapse the meta + actions wrappers when there's nothing projected. */\n .mj-page-header-interior__meta:empty,\n .mj-page-header-interior__actions:empty {\n display: none;\n }\n\n /* Collapse the identity column entirely when title/subtitle aren't set AND\n no [meta] content is projected \u2014 covers the action-band-only sub-pages\n (Dev Tools inspectors, SQL Logging) where the card is pure actions. */\n .mj-page-header-interior__identity:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n /* No .mj-btn reset here. Buttons projected into the chrome inherit the\n mjButton directive's global styles from button.scss \u2014 see \"Button Styling\"\n in packages/Angular/CLAUDE.md. */\n\n /* Mobile compaction. On a phone the parent shell's rail already names the\n section, so the interior card becomes a pure control surface: no outer\n margin (flush to the body), no title/subtitle, action buttons collapse to\n icons, and the toolbar control bar stays on one line (scrolls instead of\n wrapping). */\n @media (max-width: 768px) {\n :host {\n margin: 0;\n }\n .mj-page-header-interior__title,\n .mj-page-header-interior__subtitle {\n display: none;\n }\n /* Action buttons go icon-only \u2014 consumers wrap their button text in\n <span class=\"mj-action-label\"> so it can hide here without touching the\n mjButton directive's own styles. */\n :host ::ng-deep .mj-page-header-interior__actions .mj-action-label {\n display: none;\n }\n /* Toolbar stays a single row on mobile. Mirrors the :has() rule above\n (same specificity, later in source so it wins). */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n flex-wrap: nowrap;\n overflow-x: auto;\n /* Left-aligned on mobile (search grows to fill). */\n justify-content: flex-start;\n }\n /* Search grows so Filter + view sit at the trailing edge of the bar. */\n :host ::ng-deep .mj-page-header-interior__row--toolbar mj-page-search {\n flex: 1;\n min-width: 0;\n }\n }\n "] }]
|
|
166
166
|
}], null, { Role: [{
|
|
167
167
|
type: Input
|
|
168
168
|
}], AriaLabel: [{
|
|
@@ -172,5 +172,5 @@ export class MJPageHeaderInteriorComponent {
|
|
|
172
172
|
}], Subtitle: [{
|
|
173
173
|
type: Input
|
|
174
174
|
}] }); })();
|
|
175
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJPageHeaderInteriorComponent, { className: "MJPageHeaderInteriorComponent", filePath: "lib/page-header-interior/page-header-interior.component.ts", lineNumber:
|
|
175
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJPageHeaderInteriorComponent, { className: "MJPageHeaderInteriorComponent", filePath: "lib/page-header-interior/page-header-interior.component.ts", lineNumber: 275 }); })();
|
|
176
176
|
//# sourceMappingURL=page-header-interior.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-header-interior.component.js","sourceRoot":"","sources":["../../../src/lib/page-header-interior/page-header-interior.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;;;;;IA6ErC,iCAA4C;IAAA,YAAW;IAAA,oBAAM;;;IAAjB,cAAW;IAAX,kCAAW;;;IAGvD,iCAA+C;IAAA,YAAc;IAAA,oBAAM;;;IAApB,cAAc;IAAd,qCAAc;;AA9EzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AA4KH,MAAM,OAAO,6BAA6B;IACxC;;;;OAIG;IACM,IAAI,GAAkB,QAAQ,CAAC;IAExC;;;;OAIG;IACM,SAAS,GAAkB,IAAI,CAAC;IAEzC;;;;OAIG;IACM,KAAK,GAAkB,IAAI,CAAC;IAErC;;;;;OAKG;IACM,QAAQ,GAAkB,IAAI,CAAC;uHA5B7B,6BAA6B;6DAA7B,6BAA6B;;YAnKlC,AADF,AAHF,iCAEmC,aAC+C,aAC/B;YAC7C,+FAAa;YAGb,+FAAgB;YAGhB,iCAA2C;YACzC,kBAAyC;YAE7C,AADE,oBAAM,EACF;YACN,4BAAmD;YACnD,iCAA8C;YAC5C,qBAA4C;YAEhD,AADE,oBAAM,EACF;YACN,kCAAgF;YAC9E,sBAA4C;YAEhD,AADE,oBAAM,EACF;;;YAlBA,eAEC;YAFD,oCAEC;YACD,cAEC;YAFD,uCAEC;;;iFA6JE,6BAA6B;cA3KzC,SAAS;2BACE,yBAAyB,cACvB,IAAI,YACN;;;;;;;;;;;;;;;;;;;;;;;;;GAyBT;;kBAqJA,KAAK;;kBAOL,KAAK;;kBAOL,KAAK;;kBAQL,KAAK;;kFA5BK,6BAA6B","sourcesContent":["import { Component, Input } from '@angular/core';\n\n/**\n * `<mj-page-header-interior>` — Interior-chrome sibling of `<mj-page-header>`.\n *\n * Same slot conventions as `<mj-page-header>` (`[meta]` / `[actions]` / `[toolbar]`)\n * so consumers learn one set of names for both surfaces. Different visual shape:\n * a single-row card with surface background + radius + shadow that sits at the\n * top of a sub-page's body, NOT a page-level chrome band.\n *\n * Use this when a sub-page renders inside a left-nav shell that already owns the\n * page-level `<mj-page-header>` — the rail's active-item highlight is the section\n * indicator, so the sub-page doesn't need its own title row; just the filter /\n * action surface in a clean card.\n *\n * ## Layout\n *\n * Two-row card. The primary row has an identity column on the left (Title →\n * Subtitle → Meta, stacked) and an actions cluster on the right; the toolbar\n * row holds dense controls (search, tab nav, filter chips) that would compete\n * with the title for space if they shared a row. When `[toolbar]` has no\n * projected content, the row collapses entirely.\n *\n * ```\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ Title [—— spacer ——] [actions] │\n * │ subtitle │\n * │ [meta] │\n * ├─────────────────────────────────────────────────────────────────────────┤\n * │ [ toolbar content … search / tab-nav / filter chips … ] │\n * └─────────────────────────────────────────────────────────────────────────┘\n * ```\n *\n * - **`[Title]`** (input, optional) — short page name. Usually redundant with\n * the parent shell's left-rail label; most sub-pages still set it as a visual\n * anchor.\n * - **`[Subtitle]`** (input, optional) — short prose explaining what the page\n * does. Recommended for pages that aren't self-explanatory from the rail\n * label alone (e.g. Dev Tools inspectors).\n * - **`[meta]`** — **below the subtitle, inside the identity column**: status\n * badges, result counts, stat pills. Reads as supporting info for the\n * identity (not a peer of the title), so the title stays the dominant\n * visual anchor. Wraps to multiple lines when there are many badges.\n * - **`[toolbar]`** — second row: search, tab nav, filter chips, view toggles\n * - **`[actions]`** — top row, right edge: filter popover, refresh, secondary\n * buttons, primary CTA\n *\n * ## Example\n *\n * ```html\n * <mj-page-header-interior Subtitle=\"Read-only snapshot of Explorer runtime state\">\n * <div toolbar>\n * <mj-page-search [Value]=\"searchTerm\" (ValueChange)=\"onSearch($event)\" />\n * <mj-filter-chip Label=\"Active\" [Active]=\"status === 'active'\"\n * (Clicked)=\"setStatus('active')\" />\n * </div>\n * <div actions>\n * <mj-filter-popover ...><mj-filter-panel ...></mj-filter-panel></mj-filter-popover>\n * <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refresh()\" />\n * <button mjButton variant=\"primary\" size=\"sm\">+ Add</button>\n * </div>\n * </mj-page-header-interior>\n * ```\n *\n * See Section 10 of `plans/explorer-chrome-conventions.md` for the full\n * \"interior chrome\" contract.\n */\n@Component({\n selector: 'mj-page-header-interior',\n standalone: true,\n template: `\n <div class=\"mj-page-header-interior\"\n [attr.role]=\"Role\"\n [attr.aria-label]=\"AriaLabel\">\n <div class=\"mj-page-header-interior__row mj-page-header-interior__row--primary\">\n <div class=\"mj-page-header-interior__identity\">\n @if (Title) {\n <div class=\"mj-page-header-interior__title\">{{ Title }}</div>\n }\n @if (Subtitle) {\n <div class=\"mj-page-header-interior__subtitle\">{{ Subtitle }}</div>\n }\n <div class=\"mj-page-header-interior__meta\">\n <ng-content select=\"[meta]\"></ng-content>\n </div>\n </div>\n <div class=\"mj-page-header-interior__spacer\"></div>\n <div class=\"mj-page-header-interior__actions\">\n <ng-content select=\"[actions]\"></ng-content>\n </div>\n </div>\n <div class=\"mj-page-header-interior__row mj-page-header-interior__row--toolbar\">\n <ng-content select=\"[toolbar]\"></ng-content>\n </div>\n </div>\n `,\n styles: [`\n :host {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n /* Slot-wrapper passthrough — any element a consumer projects with the\n \\`meta\\`, \\`actions\\`, or \\`toolbar\\` attribute (regardless of class name)\n becomes a transparent container so its children become direct flex\n children of the row wrapper and inherit the row's gap. Matches the\n same pattern <mj-page-header> uses. */\n :host ::ng-deep [meta],\n :host ::ng-deep [actions],\n :host ::ng-deep [toolbar] {\n display: contents;\n }\n\n\n /* Rows: primary (identity + actions) and toolbar (separate row).\n Primary row centers actions vertically against the identity column\n (title → subtitle → optional meta). When meta is present the actions\n drift down slightly — acceptable trade for the more common no-meta\n case where flex-start looks top-heavy. */\n .mj-page-header-interior__row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary {\n gap: var(--mj-space-4);\n }\n\n /* Toolbar row — hidden by default, opt-in via consumer projection.\n\n Defaulting to display:none (instead of \"show then hide when empty\")\n eliminates the dangling divider + empty band on sub-pages that have\n no toolbar content (Dev Tools inspectors, action-only sections, etc.).\n The row only appears when the consumer projects a [toolbar] element\n that actually has content.\n\n ::ng-deep is required because the projected [toolbar] wrapper carries\n the CONSUMER's view-encapsulation scope, not ours — a scoped selector\n for [toolbar] would never match the projected wrapper, so we have to\n escape our scope to see it. The matched-content rule restores all\n the row's intended styling (flex layout, padding, divider). */\n .mj-page-header-interior__row--toolbar {\n display: none;\n }\n\n /* Nested :has(... :has(...)) is disallowed by browsers (Chromium throws\n SyntaxError; the rule is silently dropped). Use a single-level :has\n that walks two generations directly: \"toolbar row containing a [toolbar]\n wrapper that itself has any element child\". */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n /* Identity typography matches <mj-page-header> exactly — same title size,\n same subtitle size, same colors. One mental model across both chrome\n surfaces. */\n .mj-page-header-interior__title {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* Meta lives in the identity column UNDER the subtitle — it's supporting\n info for the identity, not a peer to the title. Wraps when there are\n many badges so it doesn't force horizontal overflow. The top margin\n gives the badge row clear separation from the subtitle so it reads as\n a distinct band of supporting data rather than a continuation of the\n prose line above. */\n .mj-page-header-interior__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n /* Collapse the meta + actions wrappers when there's nothing projected. */\n .mj-page-header-interior__meta:empty,\n .mj-page-header-interior__actions:empty {\n display: none;\n }\n\n /* Collapse the identity column entirely when title/subtitle aren't set AND\n no [meta] content is projected — covers the action-band-only sub-pages\n (Dev Tools inspectors, SQL Logging) where the card is pure actions. */\n .mj-page-header-interior__identity:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n /* No .mj-btn reset here. Buttons projected into the chrome inherit the\n mjButton directive's global styles from button.scss — see \"Button Styling\"\n in packages/Angular/CLAUDE.md. */\n `]\n})\nexport class MJPageHeaderInteriorComponent {\n /**\n * ARIA role applied to the rendered card. Defaults to `'search'` since the\n * dominant use case is a filter / search region. Pass `null` to omit the\n * role attribute (for non-filter chrome — e.g. a settings-only action band).\n */\n @Input() Role: string | null = 'search';\n\n /**\n * Accessible label for the rendered card. When set, the card becomes a\n * labeled landmark for assistive tech. Recommended to set this for any\n * chrome that represents a meaningful region (e.g. \"Filter users\").\n */\n @Input() AriaLabel: string | null = null;\n\n /**\n * Optional short page name rendered as the identity block on the left of the\n * card. Usually redundant with the parent shell's left-rail label, so most\n * sub-pages omit this and rely on Subtitle alone.\n */\n @Input() Title: string | null = null;\n\n /**\n * Optional one-line description rendered under Title (or alone, if Title is\n * not set). Recommended for pages whose purpose isn't obvious from the rail\n * label — e.g. Dev Tools inspectors. Keep it short — one line at typical\n * widths.\n */\n @Input() Subtitle: string | null = null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"page-header-interior.component.js","sourceRoot":"","sources":["../../../src/lib/page-header-interior/page-header-interior.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;;;;;IA6ErC,iCAA4C;IAAA,YAAW;IAAA,oBAAM;;;IAAjB,cAAW;IAAX,kCAAW;;;IAGvD,iCAA+C;IAAA,YAAc;IAAA,oBAAM;;;IAApB,cAAc;IAAd,qCAAc;;AA9EzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAgNH,MAAM,OAAO,6BAA6B;IACxC;;;;OAIG;IACM,IAAI,GAAkB,QAAQ,CAAC;IAExC;;;;OAIG;IACM,SAAS,GAAkB,IAAI,CAAC;IAEzC;;;;OAIG;IACM,KAAK,GAAkB,IAAI,CAAC;IAErC;;;;;OAKG;IACM,QAAQ,GAAkB,IAAI,CAAC;uHA5B7B,6BAA6B;6DAA7B,6BAA6B;;YAvMlC,AADF,AAHF,iCAEmC,aAC+C,aAC/B;YAC7C,+FAAa;YAGb,+FAAgB;YAGhB,iCAA2C;YACzC,kBAAyC;YAE7C,AADE,oBAAM,EACF;YACN,4BAAmD;YACnD,iCAA8C;YAC5C,qBAA4C;YAEhD,AADE,oBAAM,EACF;YACN,kCAAgF;YAC9E,sBAA4C;YAEhD,AADE,oBAAM,EACF;;;YAlBA,eAEC;YAFD,oCAEC;YACD,cAEC;YAFD,uCAEC;;;iFAiME,6BAA6B;cA/MzC,SAAS;2BACE,yBAAyB,cACvB,IAAI,YACN;;;;;;;;;;;;;;;;;;;;;;;;;GAyBT;;kBAyLA,KAAK;;kBAOL,KAAK;;kBAOL,KAAK;;kBAQL,KAAK;;kFA5BK,6BAA6B","sourcesContent":["import { Component, Input } from '@angular/core';\n\n/**\n * `<mj-page-header-interior>` — Interior-chrome sibling of `<mj-page-header>`.\n *\n * Same slot conventions as `<mj-page-header>` (`[meta]` / `[actions]` / `[toolbar]`)\n * so consumers learn one set of names for both surfaces. Different visual shape:\n * a single-row card with surface background + radius + shadow that sits at the\n * top of a sub-page's body, NOT a page-level chrome band.\n *\n * Use this when a sub-page renders inside a left-nav shell that already owns the\n * page-level `<mj-page-header>` — the rail's active-item highlight is the section\n * indicator, so the sub-page doesn't need its own title row; just the filter /\n * action surface in a clean card.\n *\n * ## Layout\n *\n * Two-row card. The primary row has an identity column on the left (Title →\n * Subtitle → Meta, stacked) and an actions cluster on the right; the toolbar\n * row holds dense controls (search, tab nav, filter chips) that would compete\n * with the title for space if they shared a row. When `[toolbar]` has no\n * projected content, the row collapses entirely.\n *\n * ```\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ Title [—— spacer ——] [actions] │\n * │ subtitle │\n * │ [meta] │\n * ├─────────────────────────────────────────────────────────────────────────┤\n * │ [ toolbar content … search / tab-nav / filter chips … ] │\n * └─────────────────────────────────────────────────────────────────────────┘\n * ```\n *\n * - **`[Title]`** (input, optional) — short page name. Usually redundant with\n * the parent shell's left-rail label; most sub-pages still set it as a visual\n * anchor.\n * - **`[Subtitle]`** (input, optional) — short prose explaining what the page\n * does. Recommended for pages that aren't self-explanatory from the rail\n * label alone (e.g. Dev Tools inspectors).\n * - **`[meta]`** — **below the subtitle, inside the identity column**: status\n * badges, result counts, stat pills. Reads as supporting info for the\n * identity (not a peer of the title), so the title stays the dominant\n * visual anchor. Wraps to multiple lines when there are many badges.\n * - **`[toolbar]`** — second row: search, tab nav, filter chips, view toggles\n * - **`[actions]`** — top row, right edge: filter popover, refresh, secondary\n * buttons, primary CTA\n *\n * ## Example\n *\n * ```html\n * <mj-page-header-interior Subtitle=\"Read-only snapshot of Explorer runtime state\">\n * <div toolbar>\n * <mj-page-search [Value]=\"searchTerm\" (ValueChange)=\"onSearch($event)\" />\n * <mj-filter-chip Label=\"Active\" [Active]=\"status === 'active'\"\n * (Clicked)=\"setStatus('active')\" />\n * </div>\n * <div actions>\n * <mj-filter-popover ...><mj-filter-panel ...></mj-filter-panel></mj-filter-popover>\n * <mj-refresh-button [Loading]=\"isLoading\" (Clicked)=\"refresh()\" />\n * <button mjButton variant=\"primary\" size=\"sm\">+ Add</button>\n * </div>\n * </mj-page-header-interior>\n * ```\n *\n * See Section 10 of `plans/explorer-chrome-conventions.md` for the full\n * \"interior chrome\" contract.\n */\n@Component({\n selector: 'mj-page-header-interior',\n standalone: true,\n template: `\n <div class=\"mj-page-header-interior\"\n [attr.role]=\"Role\"\n [attr.aria-label]=\"AriaLabel\">\n <div class=\"mj-page-header-interior__row mj-page-header-interior__row--primary\">\n <div class=\"mj-page-header-interior__identity\">\n @if (Title) {\n <div class=\"mj-page-header-interior__title\">{{ Title }}</div>\n }\n @if (Subtitle) {\n <div class=\"mj-page-header-interior__subtitle\">{{ Subtitle }}</div>\n }\n <div class=\"mj-page-header-interior__meta\">\n <ng-content select=\"[meta]\"></ng-content>\n </div>\n </div>\n <div class=\"mj-page-header-interior__spacer\"></div>\n <div class=\"mj-page-header-interior__actions\">\n <ng-content select=\"[actions]\"></ng-content>\n </div>\n </div>\n <div class=\"mj-page-header-interior__row mj-page-header-interior__row--toolbar\">\n <ng-content select=\"[toolbar]\"></ng-content>\n </div>\n </div>\n `,\n styles: [`\n :host {\n display: block;\n margin: var(--mj-space-4) var(--mj-space-4) var(--mj-space-3);\n }\n\n .mj-page-header-interior {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n }\n\n /* Slot-wrapper passthrough — any element a consumer projects with the\n \\`meta\\`, \\`actions\\`, or \\`toolbar\\` attribute (regardless of class name)\n becomes a transparent container so its children become direct flex\n children of the row wrapper and inherit the row's gap. Matches the\n same pattern <mj-page-header> uses. */\n :host ::ng-deep [meta],\n :host ::ng-deep [actions],\n :host ::ng-deep [toolbar] {\n display: contents;\n }\n\n\n /* Rows: primary (identity + actions) and toolbar (separate row).\n Primary row centers actions vertically against the identity column\n (title → subtitle → optional meta). When meta is present the actions\n drift down slightly — acceptable trade for the more common no-meta\n case where flex-start looks top-heavy. */\n .mj-page-header-interior__row {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n }\n\n .mj-page-header-interior__row--primary {\n gap: var(--mj-space-4);\n }\n\n /* Toolbar row — hidden by default, opt-in via consumer projection.\n\n Defaulting to display:none (instead of \"show then hide when empty\")\n eliminates the dangling divider + empty band on sub-pages that have\n no toolbar content (Dev Tools inspectors, action-only sections, etc.).\n The row only appears when the consumer projects a [toolbar] element\n that actually has content.\n\n ::ng-deep is required because the projected [toolbar] wrapper carries\n the CONSUMER's view-encapsulation scope, not ours — a scoped selector\n for [toolbar] would never match the projected wrapper, so we have to\n escape our scope to see it. The matched-content rule restores all\n the row's intended styling (flex layout, padding, divider). */\n .mj-page-header-interior__row--toolbar {\n display: none;\n }\n\n /* Nested :has(... :has(...)) is disallowed by browsers (Chromium throws\n SyntaxError; the rule is silently dropped). Use a single-level :has\n that walks two generations directly: \"toolbar row containing a [toolbar]\n wrapper that itself has any element child\". */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n /* Right-aligned control bar on desktop; mobile block resets to flex-start. */\n justify-content: flex-end;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .mj-page-header-interior__identity {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 2px;\n flex-shrink: 1;\n min-width: 0;\n }\n\n /* Identity typography matches <mj-page-header> exactly — same title size,\n same subtitle size, same colors. One mental model across both chrome\n surfaces. */\n .mj-page-header-interior__title {\n font-size: var(--mj-text-xl);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n line-height: 1.25;\n }\n .mj-page-header-interior__subtitle {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.4;\n }\n\n .mj-page-header-interior__spacer {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* Meta lives in the identity column UNDER the subtitle — it's supporting\n info for the identity, not a peer to the title. Wraps when there are\n many badges so it doesn't force horizontal overflow. The top margin\n gives the badge row clear separation from the subtitle so it reads as\n a distinct band of supporting data rather than a continuation of the\n prose line above. */\n .mj-page-header-interior__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--mj-space-2);\n margin-top: var(--mj-space-2);\n }\n\n .mj-page-header-interior__actions {\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-3);\n flex-shrink: 0;\n }\n\n /* Collapse the meta + actions wrappers when there's nothing projected. */\n .mj-page-header-interior__meta:empty,\n .mj-page-header-interior__actions:empty {\n display: none;\n }\n\n /* Collapse the identity column entirely when title/subtitle aren't set AND\n no [meta] content is projected — covers the action-band-only sub-pages\n (Dev Tools inspectors, SQL Logging) where the card is pure actions. */\n .mj-page-header-interior__identity:not(:has(.mj-page-header-interior__title)):not(:has(.mj-page-header-interior__subtitle)):has(.mj-page-header-interior__meta:empty) {\n display: none;\n }\n\n /* No .mj-btn reset here. Buttons projected into the chrome inherit the\n mjButton directive's global styles from button.scss — see \"Button Styling\"\n in packages/Angular/CLAUDE.md. */\n\n /* Mobile compaction. On a phone the parent shell's rail already names the\n section, so the interior card becomes a pure control surface: no outer\n margin (flush to the body), no title/subtitle, action buttons collapse to\n icons, and the toolbar control bar stays on one line (scrolls instead of\n wrapping). */\n @media (max-width: 768px) {\n :host {\n margin: 0;\n }\n .mj-page-header-interior__title,\n .mj-page-header-interior__subtitle {\n display: none;\n }\n /* Action buttons go icon-only — consumers wrap their button text in\n <span class=\"mj-action-label\"> so it can hide here without touching the\n mjButton directive's own styles. */\n :host ::ng-deep .mj-page-header-interior__actions .mj-action-label {\n display: none;\n }\n /* Toolbar stays a single row on mobile. Mirrors the :has() rule above\n (same specificity, later in source so it wins). */\n :host ::ng-deep .mj-page-header-interior__row--toolbar:has(> [toolbar] > *) {\n flex-wrap: nowrap;\n overflow-x: auto;\n /* Left-aligned on mobile (search grows to fill). */\n justify-content: flex-start;\n }\n /* Search grows so Filter + view sit at the trailing edge of the bar. */\n :host ::ng-deep .mj-page-header-interior__row--toolbar mj-page-search {\n flex: 1;\n min-width: 0;\n }\n }\n `]\n})\nexport class MJPageHeaderInteriorComponent {\n /**\n * ARIA role applied to the rendered card. Defaults to `'search'` since the\n * dominant use case is a filter / search region. Pass `null` to omit the\n * role attribute (for non-filter chrome — e.g. a settings-only action band).\n */\n @Input() Role: string | null = 'search';\n\n /**\n * Accessible label for the rendered card. When set, the card becomes a\n * labeled landmark for assistive tech. Recommended to set this for any\n * chrome that represents a meaningful region (e.g. \"Filter users\").\n */\n @Input() AriaLabel: string | null = null;\n\n /**\n * Optional short page name rendered as the identity block on the left of the\n * card. Usually redundant with the parent shell's left-rail label, so most\n * sub-pages omit this and rely on Subtitle alone.\n */\n @Input() Title: string | null = null;\n\n /**\n * Optional one-line description rendered under Title (or alone, if Title is\n * not set). Recommended for pages whose purpose isn't obvious from the rail\n * label — e.g. Dev Tools inspectors. Keep it short — one line at typical\n * widths.\n */\n @Input() Subtitle: string | null = null;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-button.component.d.ts","sourceRoot":"","sources":["../../../src/lib/refresh-button/refresh-button.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAiB,MAAM,eAAe,CAAC;;AAGvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,
|
|
1
|
+
{"version":3,"file":"refresh-button.component.d.ts","sourceRoot":"","sources":["../../../src/lib/refresh-button/refresh-button.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAiB,MAAM,eAAe,CAAC;;AAGvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAgCa,wBAAwB;IACnC,4FAA4F;IACnF,OAAO,EAAE,OAAO,CAAS;IAElC,gDAAgD;IACvC,QAAQ,EAAE,OAAO,CAAS;IAEnC,2FAA2F;IAClF,KAAK,EAAE,MAAM,CAAa;IAEnC,iDAAiD;IACxC,KAAK,EAAE,MAAM,CAAa;IAEnC,8DAA8D;IACrD,SAAS,EAAE,OAAO,CAAQ;IAEnC,kEAAkE;IACzD,OAAO,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAe;IAEtF,qFAAqF;IAC5E,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAQ;IAEzC,gFAAgF;IACtE,OAAO,2BAAkC;IAEnD,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;yCAzBrB,wBAAwB;2CAAxB,wBAAwB;CA6BpC"}
|
|
@@ -2,10 +2,13 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
|
2
2
|
import { MJButtonDirective } from '../button/button.directive';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
function MJRefreshButtonComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
5
|
-
i0.ɵɵ
|
|
5
|
+
i0.ɵɵelementStart(0, "span", 2);
|
|
6
|
+
i0.ɵɵtext(1);
|
|
7
|
+
i0.ɵɵelementEnd();
|
|
6
8
|
} if (rf & 2) {
|
|
7
9
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
8
|
-
i0.ɵɵ
|
|
10
|
+
i0.ɵɵadvance();
|
|
11
|
+
i0.ɵɵtextInterpolate(ctx_r0.Label);
|
|
9
12
|
} }
|
|
10
13
|
/**
|
|
11
14
|
* mj-refresh-button — Canonical Refresh button for the `[actions]` slot of
|
|
@@ -52,20 +55,20 @@ export class MJRefreshButtonComponent {
|
|
|
52
55
|
this.Clicked.emit(event);
|
|
53
56
|
}
|
|
54
57
|
static ɵfac = function MJRefreshButtonComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJRefreshButtonComponent)(); };
|
|
55
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJRefreshButtonComponent, selectors: [["mj-refresh-button"]], inputs: { Loading: "Loading", Disabled: "Disabled", Label: "Label", Title: "Title", ShowLabel: "ShowLabel", Variant: "Variant", Size: "Size" }, outputs: { Clicked: "Clicked" }, decls: 3, vars: 8, consts: [["type", "button", "mjButton", "", 3, "click", "variant", "size", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-arrows-rotate"]], template: function MJRefreshButtonComponent_Template(rf, ctx) { if (rf & 1) {
|
|
58
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJRefreshButtonComponent, selectors: [["mj-refresh-button"]], inputs: { Loading: "Loading", Disabled: "Disabled", Label: "Label", Title: "Title", ShowLabel: "ShowLabel", Variant: "Variant", Size: "Size" }, outputs: { Clicked: "Clicked" }, decls: 3, vars: 8, consts: [["type", "button", "mjButton", "", 3, "click", "variant", "size", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-arrows-rotate"], [1, "mj-refresh-button__label"]], template: function MJRefreshButtonComponent_Template(rf, ctx) { if (rf & 1) {
|
|
56
59
|
i0.ɵɵelementStart(0, "button", 0);
|
|
57
60
|
i0.ɵɵlistener("click", function MJRefreshButtonComponent_Template_button_click_0_listener($event) { return ctx.OnClick($event); });
|
|
58
61
|
i0.ɵɵelement(1, "i", 1);
|
|
59
|
-
i0.ɵɵconditionalCreate(2, MJRefreshButtonComponent_Conditional_2_Template, 1,
|
|
62
|
+
i0.ɵɵconditionalCreate(2, MJRefreshButtonComponent_Conditional_2_Template, 2, 1, "span", 2);
|
|
60
63
|
i0.ɵɵelementEnd();
|
|
61
64
|
} if (rf & 2) {
|
|
62
65
|
i0.ɵɵproperty("variant", ctx.Variant)("size", ctx.Size)("disabled", ctx.Disabled || ctx.Loading);
|
|
63
|
-
i0.ɵɵattribute("title", ctx.Title)("aria-label",
|
|
66
|
+
i0.ɵɵattribute("title", ctx.Title)("aria-label", ctx.Title);
|
|
64
67
|
i0.ɵɵadvance();
|
|
65
68
|
i0.ɵɵclassProp("fa-spin", ctx.Loading);
|
|
66
69
|
i0.ɵɵadvance();
|
|
67
70
|
i0.ɵɵconditional(ctx.ShowLabel ? 2 : -1);
|
|
68
|
-
} }, dependencies: [MJButtonDirective], styles: ["[_nghost-%COMP%] { display: inline-flex; }"] });
|
|
71
|
+
} }, dependencies: [MJButtonDirective], styles: ["[_nghost-%COMP%] { display: inline-flex; }\n\n \n\n\n @media (max-width: 768px) {\n .mj-refresh-button__label[_ngcontent-%COMP%] {\n display: none;\n }\n }"] });
|
|
69
72
|
}
|
|
70
73
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJRefreshButtonComponent, [{
|
|
71
74
|
type: Component,
|
|
@@ -77,14 +80,14 @@ export class MJRefreshButtonComponent {
|
|
|
77
80
|
[size]="Size"
|
|
78
81
|
[disabled]="Disabled || Loading"
|
|
79
82
|
[attr.title]="Title"
|
|
80
|
-
[attr.aria-label]="
|
|
83
|
+
[attr.aria-label]="Title"
|
|
81
84
|
(click)="OnClick($event)">
|
|
82
85
|
<i class="fa-solid fa-arrows-rotate" [class.fa-spin]="Loading" aria-hidden="true"></i>
|
|
83
86
|
@if (ShowLabel) {
|
|
84
|
-
{{ Label }}
|
|
87
|
+
<span class="mj-refresh-button__label">{{ Label }}</span>
|
|
85
88
|
}
|
|
86
89
|
</button>
|
|
87
|
-
`, styles: ["\n :host { display: inline-flex; }\n "] }]
|
|
90
|
+
`, styles: ["\n :host { display: inline-flex; }\n\n /* Icon-only on mobile \u2014 the spinning arrows + aria-label carry it; the\n text just costs width in the compact chrome control row. */\n @media (max-width: 768px) {\n .mj-refresh-button__label {\n display: none;\n }\n }\n "] }]
|
|
88
91
|
}], null, { Loading: [{
|
|
89
92
|
type: Input
|
|
90
93
|
}], Disabled: [{
|
|
@@ -102,5 +105,5 @@ export class MJRefreshButtonComponent {
|
|
|
102
105
|
}], Clicked: [{
|
|
103
106
|
type: Output
|
|
104
107
|
}] }); })();
|
|
105
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJRefreshButtonComponent, { className: "MJRefreshButtonComponent", filePath: "lib/refresh-button/refresh-button.component.ts", lineNumber:
|
|
108
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJRefreshButtonComponent, { className: "MJRefreshButtonComponent", filePath: "lib/refresh-button/refresh-button.component.ts", lineNumber: 58 }); })();
|
|
106
109
|
//# sourceMappingURL=refresh-button.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-button.component.js","sourceRoot":"","sources":["../../../src/lib/refresh-button/refresh-button.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;;;IAwCvD,
|
|
1
|
+
{"version":3,"file":"refresh-button.component.js","sourceRoot":"","sources":["../../../src/lib/refresh-button/refresh-button.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;;;IAwCvD,+BAAuC;IAAA,YAAW;IAAA,iBAAO;;;IAAlB,cAAW;IAAX,kCAAW;;AAtC1D;;;;;;;;;;;;;;;;;;;;;GAqBG;AAiCH,MAAM,OAAO,wBAAwB;IACnC,4FAA4F;IACnF,OAAO,GAAY,KAAK,CAAC;IAElC,gDAAgD;IACvC,QAAQ,GAAY,KAAK,CAAC;IAEnC,2FAA2F;IAClF,KAAK,GAAW,SAAS,CAAC;IAEnC,iDAAiD;IACxC,KAAK,GAAW,SAAS,CAAC;IAEnC,8DAA8D;IACrD,SAAS,GAAY,IAAI,CAAC;IAEnC,kEAAkE;IACzD,OAAO,GAA0D,WAAW,CAAC;IAEtF,qFAAqF;IAC5E,IAAI,GAAuB,IAAI,CAAC;IAEzC,gFAAgF;IACtE,OAAO,GAAG,IAAI,YAAY,EAAc,CAAC;IAEnD,OAAO,CAAC,KAAiB;QACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;kHA5BU,wBAAwB;6DAAxB,wBAAwB;YA3BjC,iCAQ4B;YAA1B,2GAAS,mBAAe,IAAC;YACzB,uBAAsF;YACtF,2FAAiB;YAGnB,iBAAS;;YARP,AADA,AADA,qCAAmB,kBACN,yCACmB;;YAIK,cAAyB;YAAzB,sCAAyB;YAC9D,cAEC;YAFD,wCAEC;4BAdK,iBAAiB;;iFA6BhB,wBAAwB;cAhCpC,SAAS;2BACE,mBAAmB,cACjB,IAAI,WACP,CAAC,iBAAiB,CAAC,YAClB;;;;;;;;;;;;;;;GAeT;;kBAeA,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,MAAM;;kFAvBI,wBAAwB","sourcesContent":["import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { MJButtonDirective } from '../button/button.directive';\n\n/**\n * mj-refresh-button — Canonical Refresh button for the `[actions]` slot of\n * `<mj-page-header>`. Encapsulates the icon + spin-on-loading behavior so\n * pages don't reinvent it every time.\n *\n * Replaces the repeated:\n * ```html\n * <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"LoadData()\" [disabled]=\"IsLoading\" title=\"Refresh\">\n * <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"IsLoading\"></i> Refresh\n * </button>\n * ```\n *\n * Usage:\n * ```html\n * <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n *\n * <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\" [ShowLabel]=\"false\" /> <!-- icon-only -->\n * ```\n *\n * The `Loading` input drives both the spinner AND the disabled state — pass\n * the page's `IsLoading` flag and you're done.\n */\n@Component({\n selector: 'mj-refresh-button',\n standalone: true,\n imports: [MJButtonDirective],\n template: `\n <button\n type=\"button\"\n mjButton\n [variant]=\"Variant\"\n [size]=\"Size\"\n [disabled]=\"Disabled || Loading\"\n [attr.title]=\"Title\"\n [attr.aria-label]=\"Title\"\n (click)=\"OnClick($event)\">\n <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"Loading\" aria-hidden=\"true\"></i>\n @if (ShowLabel) {\n <span class=\"mj-refresh-button__label\">{{ Label }}</span>\n }\n </button>\n `,\n styles: [`\n :host { display: inline-flex; }\n\n /* Icon-only on mobile — the spinning arrows + aria-label carry it; the\n text just costs width in the compact chrome control row. */\n @media (max-width: 768px) {\n .mj-refresh-button__label {\n display: none;\n }\n }\n `]\n})\nexport class MJRefreshButtonComponent {\n /** When true, the icon spins and the button is disabled. Pass `IsLoading` from the page. */\n @Input() Loading: boolean = false;\n\n /** Force-disable independently of `Loading`. */\n @Input() Disabled: boolean = false;\n\n /** Visible button text. Defaults to \"Refresh\". Use `[ShowLabel]=\"false\"` for icon-only. */\n @Input() Label: string = 'Refresh';\n\n /** Hover/aria tooltip. Defaults to \"Refresh\". */\n @Input() Title: string = 'Refresh';\n\n /** Show the text label next to the icon. Defaults to true. */\n @Input() ShowLabel: boolean = true;\n\n /** Button variant passed to mjButton. Defaults to 'secondary'. */\n @Input() Variant: 'primary' | 'secondary' | 'outline' | 'flat' | 'icon' = 'secondary';\n\n /** Button size passed to mjButton. Defaults to 'sm' to match page-header density. */\n @Input() Size: 'sm' | 'md' | 'lg' = 'sm';\n\n /** Emitted when the button is clicked AND the button isn't disabled/loading. */\n @Output() Clicked = new EventEmitter<MouseEvent>();\n\n OnClick(event: MouseEvent): void {\n if (this.Disabled || this.Loading) return;\n this.Clicked.emit(event);\n }\n}\n"]}
|