fold-ng 0.2.0 → 0.3.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/CHANGELOG.md +72 -1
- package/README.md +11 -3
- package/fesm2022/fold-ng.mjs +362 -76
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/tokens/scales.css +8 -0
- package/types/fold-ng.d.ts +294 -65
- package/types/fold-ng.d.ts.map +1 -1
- package/tsconfig.lib.tsbuildinfo +0 -1
package/fesm2022/fold-ng.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DOCUMENT, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, ViewContainerRef, Injector } from '@angular/core';
|
|
3
|
+
import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, viewChildren, ViewContainerRef, Injector } from '@angular/core';
|
|
4
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A matched element is only truly focusable if it's rendered — `display:none`
|
|
@@ -488,7 +489,7 @@ class FoldAppShellComponent {
|
|
|
488
489
|
headerHeightMobileVar = computed(() => pxVar(this.headerHeightMobile()), /* @ts-ignore */
|
|
489
490
|
...(ngDevMode ? [{ debugName: "headerHeightMobileVar" }] : /* istanbul ignore next */ []));
|
|
490
491
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
491
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAppShellComponent, isStandalone: true, selector: "fold-app-shell", inputs: { railWidth: { classPropertyName: "railWidth", publicName: "railWidth", isSignal: true, isRequired: false, transformFunction: null }, headerHeight: { classPropertyName: "headerHeight", publicName: "headerHeight", isSignal: true, isRequired: false, transformFunction: null }, headerHeightMobile: { classPropertyName: "headerHeightMobile", publicName: "headerHeightMobile", isSignal: true, isRequired: false, transformFunction: null }, headerLayout: { classPropertyName: "headerLayout", publicName: "headerLayout", isSignal: true, isRequired: false, transformFunction: null }, footerLayout: { classPropertyName: "footerLayout", publicName: "footerLayout", isSignal: true, isRequired: false, transformFunction: null }, footerBehavior: { classPropertyName: "footerBehavior", publicName: "footerBehavior", isSignal: true, isRequired: false, transformFunction: null }, contentScroll: { classPropertyName: "contentScroll", publicName: "contentScroll", isSignal: true, isRequired: false, transformFunction: null }, skipLinkLabel: { classPropertyName: "skipLinkLabel", publicName: "skipLinkLabel", isSignal: true, isRequired: false, transformFunction: null }, drawerLabel: { classPropertyName: "drawerLabel", publicName: "drawerLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileNav: { classPropertyName: "mobileNav", publicName: "mobileNav", isSignal: true, isRequired: false, transformFunction: null }, mobileNavOpen: { classPropertyName: "mobileNavOpen", publicName: "mobileNavOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mobileNavOpen: "mobileNavOpenChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "style.--fold-shell-rail-width": "railWidthVar()", "style.--fold-shell-header-height": "headerHeightVar()", "style.--fold-shell-header-height-mobile": "headerHeightMobileVar()", "class.header-full": "headerLayout() === \"full\"", "class.footer-full": "footerLayout() === \"full\"", "class.footer-scroll": "footerBehavior() === \"scroll\"", "class.mobile-drawer": "mobileNav() === \"drawer\"", "class.mobile-nav-open": "drawerOpen()", "class.content-auto": "contentScroll() === \"auto\"" } }, exportAs: ["foldAppShell"], ngImport: i0, template: "<!-- Skip-link: the first Tab stop \u2014 visually hidden until focused, then jumps\n keyboard users past the rails to <main> (which is focusable, tabindex=-1). -->\n<a class=\"skip-link\" [href]=\"'#' + contentId\" (click)=\"skipToContent($event)\">{{\n skipLinkLabel()\n}}</a>\n\n<!-- The primary rail. While the mobile drawer is open it is a real modal dialog\n (named, aria-modal, focus-trapped) and every sibling region is inert \u2014 the\n same bar the panel host holds; on desktop it's a plain nav rail. -->\n<div\n class=\"rail-primary\"\n [id]=\"drawerId\"\n foldSurface=\"chrome\"\n [foldFocusTrap]=\"drawerOpen()\"\n [attr.role]=\"drawerOpen() ? 'dialog' : null\"\n [attr.aria-modal]=\"drawerOpen() ? 'true' : null\"\n [attr.aria-label]=\"drawerOpen() ? drawerLabel() : null\"\n tabindex=\"-1\"\n>\n <ng-content select=\"[railPrimary]\" />\n</div>\n<div class=\"rail-secondary\" foldSurface=\"chrome\" [inert]=\"drawerOpen()\">\n <ng-content select=\"[railSecondary]\" />\n</div>\n\n<!-- Mobile drawer scrim \u2014 only rendered while the drawer is actually open (the\n shell is narrow AND mobileNavOpen). A darkening veil that dismisses on\n click; Escape and widening close it too. aria-hidden: the focus-trapped\n drawer + Escape are the accessible path, the scrim is a mouse convenience. -->\n@if (drawerOpen()) {\n <div class=\"mobile-scrim\" (click)=\"closeMobileNav()\" aria-hidden=\"true\"></div>\n}\n<header class=\"header\" foldSurface=\"chrome\" [inert]=\"drawerOpen()\">\n <ng-content select=\"[header]\" />\n</header>\n\n<!-- One footer, stamped in one of two positions \u2014 a grid row (pinned, always in\n sight) or inside the content scroll (revealed on the way down). A single\n <ng-content> in a template avoids duplicate-selector projection loss. -->\n<ng-template #footerTpl>\n <footer\n class=\"footer\"\n [class.footer-inflow]=\"footerBehavior() === 'scroll'\"\n foldSurface=\"chrome\"\n [inert]=\"drawerOpen()\"\n >\n <ng-content select=\"[footer]\" />\n </footer>\n</ng-template>\n\n<main\n class=\"content\"\n foldSurface=\"page\"\n [id]=\"contentId\"\n tabindex=\"-1\"\n [inert]=\"drawerOpen()\"\n>\n <ng-content />\n @if (footerBehavior() === \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n }\n</main>\n@if (footerBehavior() !== \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;height:100%;width:100%;position:relative;box-sizing:border-box;overflow:hidden;background:var(--fold-color-bg-page);grid-template-columns:auto auto minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height, 56px) 1fr;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\"}.skip-link{position:absolute;top:0;left:0;z-index:60;margin:var(--fold-space-sm, 8px);padding:8px 14px;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);color:var(--fold-color-text);font-size:var(--fold-text-sm);font-weight:600;text-decoration:none;transform:translateY(-150%);transition:transform .12s ease}.skip-link:focus{transform:translateY(0)}@media(prefers-reduced-motion:reduce){.skip-link{transition:none}}:host(.header-full){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\"}:host(:not(.footer-scroll):has([footer])){grid-template-rows:var(--fold-shell-header-height, 56px) 1fr auto;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.header-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}.rail-primary{grid-area:rail-primary;min-height:0}.rail-secondary{grid-area:rail-secondary;min-height:0}.header{grid-area:header;min-width:0}.footer{grid-area:footer;min-width:0;min-height:0}.content{grid-area:content;position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.content:focus{outline:none}:host(.footer-scroll) .content,:host(.content-auto) .content{overflow:auto}.footer-inflow{margin-top:auto}.rail-primary:has([data-elevated]),.rail-secondary:has([data-elevated]),.header:has([data-elevated]),.footer:has([data-elevated]),.content:has([data-elevated]){padding:var(--fold-surface-inset, var(--fold-space-md))}@media(max-width:768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@container (max-width: 1024px){.rail-secondary{display:none}}@container (max-width: 768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@keyframes fold-shell-scrim-in{0%{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.rail-primary,.mobile-scrim{transition:none;animation:none}}\n"], dependencies: [{ kind: "directive", type: FoldSurfaceDirective, selector: "[foldSurface]", inputs: ["foldSurface"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: FocusTrapDirective, selector: "[foldFocusTrap]", inputs: ["foldFocusTrap"] }] });
|
|
492
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAppShellComponent, isStandalone: true, selector: "fold-app-shell", inputs: { railWidth: { classPropertyName: "railWidth", publicName: "railWidth", isSignal: true, isRequired: false, transformFunction: null }, headerHeight: { classPropertyName: "headerHeight", publicName: "headerHeight", isSignal: true, isRequired: false, transformFunction: null }, headerHeightMobile: { classPropertyName: "headerHeightMobile", publicName: "headerHeightMobile", isSignal: true, isRequired: false, transformFunction: null }, headerLayout: { classPropertyName: "headerLayout", publicName: "headerLayout", isSignal: true, isRequired: false, transformFunction: null }, footerLayout: { classPropertyName: "footerLayout", publicName: "footerLayout", isSignal: true, isRequired: false, transformFunction: null }, footerBehavior: { classPropertyName: "footerBehavior", publicName: "footerBehavior", isSignal: true, isRequired: false, transformFunction: null }, contentScroll: { classPropertyName: "contentScroll", publicName: "contentScroll", isSignal: true, isRequired: false, transformFunction: null }, skipLinkLabel: { classPropertyName: "skipLinkLabel", publicName: "skipLinkLabel", isSignal: true, isRequired: false, transformFunction: null }, drawerLabel: { classPropertyName: "drawerLabel", publicName: "drawerLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileNav: { classPropertyName: "mobileNav", publicName: "mobileNav", isSignal: true, isRequired: false, transformFunction: null }, mobileNavOpen: { classPropertyName: "mobileNavOpen", publicName: "mobileNavOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mobileNavOpen: "mobileNavOpenChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "style.--fold-shell-rail-width": "railWidthVar()", "style.--fold-shell-header-height": "headerHeightVar()", "style.--fold-shell-header-height-mobile": "headerHeightMobileVar()", "class.header-full": "headerLayout() === \"full\"", "class.footer-full": "footerLayout() === \"full\"", "class.footer-scroll": "footerBehavior() === \"scroll\"", "class.mobile-drawer": "mobileNav() === \"drawer\"", "class.mobile-nav-open": "drawerOpen()", "class.content-auto": "contentScroll() === \"auto\"" } }, exportAs: ["foldAppShell"], ngImport: i0, template: "<!-- Skip-link: the first Tab stop \u2014 visually hidden until focused, then jumps\n keyboard users past the rails to <main> (which is focusable, tabindex=-1). -->\n<a class=\"skip-link\" [href]=\"'#' + contentId\" (click)=\"skipToContent($event)\">{{\n skipLinkLabel()\n}}</a>\n\n<!-- The primary rail. While the mobile drawer is open it is a real modal dialog\n (named, aria-modal, focus-trapped) and every sibling region is inert \u2014 the\n same bar the panel host holds; on desktop it's a plain nav rail. -->\n<div\n class=\"rail-primary\"\n [id]=\"drawerId\"\n foldSurface=\"chrome\"\n [foldFocusTrap]=\"drawerOpen()\"\n [attr.role]=\"drawerOpen() ? 'dialog' : null\"\n [attr.aria-modal]=\"drawerOpen() ? 'true' : null\"\n [attr.aria-label]=\"drawerOpen() ? drawerLabel() : null\"\n tabindex=\"-1\"\n>\n <ng-content select=\"[railPrimary]\" />\n</div>\n<div\n class=\"rail-secondary\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content select=\"[railSecondary]\" />\n</div>\n\n<!-- Mobile drawer scrim \u2014 only rendered while the drawer is actually open (the\n shell is narrow AND mobileNavOpen). A darkening veil that dismisses on\n click; Escape and widening close it too. aria-hidden: the focus-trapped\n drawer + Escape are the accessible path, the scrim is a mouse convenience. -->\n@if (drawerOpen()) {\n <div class=\"mobile-scrim\" (click)=\"closeMobileNav()\" aria-hidden=\"true\"></div>\n}\n<header\n class=\"header\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content select=\"[header]\" />\n</header>\n\n<!-- One footer, stamped in one of two positions \u2014 a grid row (pinned, always in\n sight) or inside the content scroll (revealed on the way down). A single\n <ng-content> in a template avoids duplicate-selector projection loss. -->\n<ng-template #footerTpl>\n <footer\n class=\"footer\"\n [class.footer-inflow]=\"footerBehavior() === 'scroll'\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n >\n <ng-content select=\"[footer]\" />\n </footer>\n</ng-template>\n\n<main\n class=\"content\"\n foldSurface=\"page\"\n [id]=\"contentId\"\n tabindex=\"-1\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content />\n @if (footerBehavior() === \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n }\n</main>\n@if (footerBehavior() !== \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;height:100%;width:100%;position:relative;box-sizing:border-box;overflow:hidden;background:var(--fold-color-bg-page);grid-template-columns:auto auto minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height, 56px) 1fr;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\"}.skip-link{position:absolute;top:0;left:0;z-index:60;margin:var(--fold-space-sm, 8px);padding:8px 14px;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);color:var(--fold-color-text);font-size:var(--fold-text-sm);font-weight:600;text-decoration:none;transform:translateY(-150%);transition:transform .12s ease}.skip-link:focus{transform:translateY(0)}@media(prefers-reduced-motion:reduce){.skip-link{transition:none}}:host(.header-full){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\"}:host(:not(.footer-scroll):has([footer])){grid-template-rows:var(--fold-shell-header-height, 56px) 1fr auto;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.header-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}.rail-primary{grid-area:rail-primary;min-height:0}.rail-secondary{grid-area:rail-secondary;min-height:0}.header{grid-area:header;min-width:0}.footer{grid-area:footer;min-width:0;min-height:0}.content{grid-area:content;position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.content:focus{outline:none}:host(.footer-scroll) .content,:host(.content-auto) .content{overflow:auto}.footer-inflow{margin-top:auto}.rail-primary:has([data-elevated]),.rail-secondary:has([data-elevated]),.header:has([data-elevated]),.footer:has([data-elevated]),.content:has([data-elevated]){padding:var(--fold-surface-inset, var(--fold-space-md))}@media(max-width:768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@container (max-width: 1024px){.rail-secondary{display:none}}@container (max-width: 768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@keyframes fold-shell-scrim-in{0%{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.rail-primary,.mobile-scrim{transition:none;animation:none}}\n"], dependencies: [{ kind: "directive", type: FoldSurfaceDirective, selector: "[foldSurface]", inputs: ["foldSurface"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: FocusTrapDirective, selector: "[foldFocusTrap]", inputs: ["foldFocusTrap"] }] });
|
|
492
493
|
}
|
|
493
494
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAppShellComponent, decorators: [{
|
|
494
495
|
type: Component,
|
|
@@ -502,7 +503,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
502
503
|
"[class.mobile-drawer]": 'mobileNav() === "drawer"',
|
|
503
504
|
"[class.mobile-nav-open]": "drawerOpen()",
|
|
504
505
|
"[class.content-auto]": 'contentScroll() === "auto"',
|
|
505
|
-
}, template: "<!-- Skip-link: the first Tab stop \u2014 visually hidden until focused, then jumps\n keyboard users past the rails to <main> (which is focusable, tabindex=-1). -->\n<a class=\"skip-link\" [href]=\"'#' + contentId\" (click)=\"skipToContent($event)\">{{\n skipLinkLabel()\n}}</a>\n\n<!-- The primary rail. While the mobile drawer is open it is a real modal dialog\n (named, aria-modal, focus-trapped) and every sibling region is inert \u2014 the\n same bar the panel host holds; on desktop it's a plain nav rail. -->\n<div\n class=\"rail-primary\"\n [id]=\"drawerId\"\n foldSurface=\"chrome\"\n [foldFocusTrap]=\"drawerOpen()\"\n [attr.role]=\"drawerOpen() ? 'dialog' : null\"\n [attr.aria-modal]=\"drawerOpen() ? 'true' : null\"\n [attr.aria-label]=\"drawerOpen() ? drawerLabel() : null\"\n tabindex=\"-1\"\n>\n <ng-content select=\"[railPrimary]\" />\n</div>\n<div
|
|
506
|
+
}, template: "<!-- Skip-link: the first Tab stop \u2014 visually hidden until focused, then jumps\n keyboard users past the rails to <main> (which is focusable, tabindex=-1). -->\n<a class=\"skip-link\" [href]=\"'#' + contentId\" (click)=\"skipToContent($event)\">{{\n skipLinkLabel()\n}}</a>\n\n<!-- The primary rail. While the mobile drawer is open it is a real modal dialog\n (named, aria-modal, focus-trapped) and every sibling region is inert \u2014 the\n same bar the panel host holds; on desktop it's a plain nav rail. -->\n<div\n class=\"rail-primary\"\n [id]=\"drawerId\"\n foldSurface=\"chrome\"\n [foldFocusTrap]=\"drawerOpen()\"\n [attr.role]=\"drawerOpen() ? 'dialog' : null\"\n [attr.aria-modal]=\"drawerOpen() ? 'true' : null\"\n [attr.aria-label]=\"drawerOpen() ? drawerLabel() : null\"\n tabindex=\"-1\"\n>\n <ng-content select=\"[railPrimary]\" />\n</div>\n<div\n class=\"rail-secondary\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content select=\"[railSecondary]\" />\n</div>\n\n<!-- Mobile drawer scrim \u2014 only rendered while the drawer is actually open (the\n shell is narrow AND mobileNavOpen). A darkening veil that dismisses on\n click; Escape and widening close it too. aria-hidden: the focus-trapped\n drawer + Escape are the accessible path, the scrim is a mouse convenience. -->\n@if (drawerOpen()) {\n <div class=\"mobile-scrim\" (click)=\"closeMobileNav()\" aria-hidden=\"true\"></div>\n}\n<header\n class=\"header\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content select=\"[header]\" />\n</header>\n\n<!-- One footer, stamped in one of two positions \u2014 a grid row (pinned, always in\n sight) or inside the content scroll (revealed on the way down). A single\n <ng-content> in a template avoids duplicate-selector projection loss. -->\n<ng-template #footerTpl>\n <footer\n class=\"footer\"\n [class.footer-inflow]=\"footerBehavior() === 'scroll'\"\n foldSurface=\"chrome\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n >\n <ng-content select=\"[footer]\" />\n </footer>\n</ng-template>\n\n<main\n class=\"content\"\n foldSurface=\"page\"\n [id]=\"contentId\"\n tabindex=\"-1\"\n [attr.inert]=\"drawerOpen() ? '' : null\"\n>\n <ng-content />\n @if (footerBehavior() === \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n }\n</main>\n@if (footerBehavior() !== \"scroll\") {\n <ng-container [ngTemplateOutlet]=\"footerTpl\" />\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;height:100%;width:100%;position:relative;box-sizing:border-box;overflow:hidden;background:var(--fold-color-bg-page);grid-template-columns:auto auto minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height, 56px) 1fr;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\"}.skip-link{position:absolute;top:0;left:0;z-index:60;margin:var(--fold-space-sm, 8px);padding:8px 14px;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);color:var(--fold-color-text);font-size:var(--fold-text-sm);font-weight:600;text-decoration:none;transform:translateY(-150%);transition:transform .12s ease}.skip-link:focus{transform:translateY(0)}@media(prefers-reduced-motion:reduce){.skip-link{transition:none}}:host(.header-full){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\"}:host(:not(.footer-scroll):has([footer])){grid-template-rows:var(--fold-shell-header-height, 56px) 1fr auto;grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.header-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"rail-primary rail-secondary footer\"}:host(.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"rail-primary rail-secondary header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-areas:\"header header header\" \"rail-primary rail-secondary content\" \"footer footer footer\"}.rail-primary{grid-area:rail-primary;min-height:0}.rail-secondary{grid-area:rail-secondary;min-height:0}.header{grid-area:header;min-width:0}.footer{grid-area:footer;min-width:0;min-height:0}.content{grid-area:content;position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.content:focus{outline:none}:host(.footer-scroll) .content,:host(.content-auto) .content{overflow:auto}.footer-inflow{margin-top:auto}.rail-primary:has([data-elevated]),.rail-secondary:has([data-elevated]),.header:has([data-elevated]),.footer:has([data-elevated]),.content:has([data-elevated]){padding:var(--fold-surface-inset, var(--fold-space-md))}@media(max-width:768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@container (max-width: 1024px){.rail-secondary{display:none}}@container (max-width: 768px){:host,:host(.header-full){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr;grid-template-areas:\"header\" \"content\"}:host(:not(.footer-scroll):has([footer])),:host(.header-full:not(.footer-scroll):has([footer])),:host(.footer-full:not(.footer-scroll):has([footer])),:host(.header-full.footer-full:not(.footer-scroll):has([footer])){grid-template-columns:minmax(0,1fr);grid-template-rows:var(--fold-shell-header-height-mobile, 52px) 1fr auto;grid-template-areas:\"header\" \"content\" \"footer\"}.rail-primary,.rail-secondary{display:none}:host(.mobile-drawer) .rail-primary{display:block;position:absolute;top:var(--fold-shell-header-height-mobile, 52px);bottom:0;left:0;z-index:41;width:auto;max-width:85%;overflow-y:auto;visibility:hidden;transform:translate(-100%);transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}:host(.mobile-drawer.mobile-nav-open) .rail-primary{visibility:visible;transform:translate(0)}.mobile-scrim{position:absolute;inset:var(--fold-shell-header-height-mobile, 52px) 0 0 0;z-index:40;border:0;padding:0;background:var(--fold-color-scrim);animation:fold-shell-scrim-in .24s ease}:host(.mobile-nav-open) .content{overflow:hidden}}@keyframes fold-shell-scrim-in{0%{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.rail-primary,.mobile-scrim{transition:none;animation:none}}\n"] }]
|
|
506
507
|
}], ctorParameters: () => [], propDecorators: { railWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "railWidth", required: false }] }], headerHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerHeight", required: false }] }], headerHeightMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerHeightMobile", required: false }] }], headerLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerLayout", required: false }] }], footerLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLayout", required: false }] }], footerBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerBehavior", required: false }] }], contentScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentScroll", required: false }] }], skipLinkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLinkLabel", required: false }] }], drawerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawerLabel", required: false }] }], mobileNav: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileNav", required: false }] }], mobileNavOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileNavOpen", required: false }] }, { type: i0.Output, args: ["mobileNavOpenChange"] }], onEscape: [{
|
|
507
508
|
type: HostListener,
|
|
508
509
|
args: ["document:keydown.escape"]
|
|
@@ -1389,7 +1390,7 @@ const FOLD_SEMANTIC_COLOR_TOKENS = [
|
|
|
1389
1390
|
"bg-rail-primary",
|
|
1390
1391
|
/** Rail 2 — the workspace menu. */
|
|
1391
1392
|
"bg-rail-secondary",
|
|
1392
|
-
/** Rail 3 — tertiary nav (e.g. a `fold-
|
|
1393
|
+
/** Rail 3 — tertiary nav (e.g. a `fold-view-nav` section sidebar). */
|
|
1393
1394
|
"bg-rail-tertiary",
|
|
1394
1395
|
/* ── Primary / accent ─────────────────────────────────────── */
|
|
1395
1396
|
/** Primary / accent — brand teal (solid). */
|
|
@@ -1472,6 +1473,10 @@ const FOLD_TEXT_TOKENS = ["xs", "sm", "md", "lg", "xl"];
|
|
|
1472
1473
|
const FOLD_ICON_SIZE_TOKENS = ["xs", "sm", "md", "lg", "xl"];
|
|
1473
1474
|
/** Space (gap / padding / margin) scale. */
|
|
1474
1475
|
const FOLD_SPACE_TOKENS = ["xs", "sm", "md", "lg", "xl"];
|
|
1476
|
+
/** Rail-width scale — the nav-rail hierarchy's shared widths, named to pair with
|
|
1477
|
+
* the `--fold-color-bg-rail-*` colours (primary app menu → secondary workspace /
|
|
1478
|
+
* aside → tertiary in-page nav). */
|
|
1479
|
+
const FOLD_RAIL_TOKENS = ["primary", "secondary", "tertiary"];
|
|
1475
1480
|
/** Motion scale — `transition` shorthands (duration + easing). */
|
|
1476
1481
|
const FOLD_MOTION_TOKENS = ["fast", "base", "slow"];
|
|
1477
1482
|
/** Backdrop-blur radii (`--fold-blur-*`). */
|
|
@@ -2421,7 +2426,7 @@ class FoldMenuComponent {
|
|
|
2421
2426
|
this.expanded.update((v) => !v);
|
|
2422
2427
|
}
|
|
2423
2428
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2424
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldMenuComponent, isStandalone: true, selector: "fold-menu", inputs: { collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, tint: { classPropertyName: "tint", publicName: "tint", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, togglePlacement: { classPropertyName: "togglePlacement", publicName: "togglePlacement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, host: { properties: { "class.expanded": "expanded()", "attr.data-tint": "tint()", "attr.data-level": "level()" } }, viewQueries: [{ propertyName: "headRef", first: true, predicate: ["head"], descendants: true, isSignal: true }, { propertyName: "footRef", first: true, predicate: ["foot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, 64px);height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
2429
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldMenuComponent, isStandalone: true, selector: "fold-menu", inputs: { collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, tint: { classPropertyName: "tint", publicName: "tint", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, togglePlacement: { classPropertyName: "togglePlacement", publicName: "togglePlacement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, host: { properties: { "class.expanded": "expanded()", "attr.data-tint": "tint()", "attr.data-level": "level()" } }, viewQueries: [{ propertyName: "headRef", first: true, predicate: ["head"], descendants: true, isSignal: true }, { propertyName: "footRef", first: true, predicate: ["foot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, var(--fold-rail-primary, 64px));height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
2425
2430
|
}
|
|
2426
2431
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldMenuComponent, decorators: [{
|
|
2427
2432
|
type: Component,
|
|
@@ -2429,7 +2434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
2429
2434
|
"[class.expanded]": "expanded()",
|
|
2430
2435
|
"[attr.data-tint]": "tint()",
|
|
2431
2436
|
"[attr.data-level]": "level()",
|
|
2432
|
-
}, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, 64px);height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"] }]
|
|
2437
|
+
}, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, var(--fold-rail-primary, 64px));height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"] }]
|
|
2433
2438
|
}], ctorParameters: () => [], propDecorators: { collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], tint: [{ type: i0.Input, args: [{ isSignal: true, alias: "tint", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], togglePlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "togglePlacement", required: false }] }], headRef: [{ type: i0.ViewChild, args: ["head", { isSignal: true }] }], footRef: [{ type: i0.ViewChild, args: ["foot", { isSignal: true }] }] } });
|
|
2434
2439
|
|
|
2435
2440
|
/** Solid accent colour per semantic tone (drives the dot + follow-pill). */
|
|
@@ -4698,23 +4703,26 @@ class FoldAsideLayoutComponent {
|
|
|
4698
4703
|
}, /* @ts-ignore */
|
|
4699
4704
|
...(ngDevMode ? [{ debugName: "topOffsetCss" }] : /* istanbul ignore next */ []));
|
|
4700
4705
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAsideLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4701
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldAsideLayoutComponent, isStandalone: true, selector: "fold-aside-layout", inputs: { stackLeftFirst: { classPropertyName: "stackLeftFirst", publicName: "stackLeftFirst", isSignal: true, isRequired: false, transformFunction: null }, asideLeftLabel: { classPropertyName: "asideLeftLabel", publicName: "asideLeftLabel", isSignal: true, isRequired: false, transformFunction: null }, asideRightLabel: { classPropertyName: "asideRightLabel", publicName: "asideRightLabel", isSignal: true, isRequired: false, transformFunction: null }, topOffset: { classPropertyName: "topOffset", publicName: "topOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--fold-aside-layout-top": "topOffsetCss()", "class.stack-left-first": "stackLeftFirst()" } }, ngImport: i0, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] });
|
|
4706
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldAsideLayoutComponent, isStandalone: true, selector: "fold-aside-layout", inputs: { stackLeftFirst: { classPropertyName: "stackLeftFirst", publicName: "stackLeftFirst", isSignal: true, isRequired: false, transformFunction: null }, asideLeftLabel: { classPropertyName: "asideLeftLabel", publicName: "asideLeftLabel", isSignal: true, isRequired: false, transformFunction: null }, asideRightLabel: { classPropertyName: "asideRightLabel", publicName: "asideRightLabel", isSignal: true, isRequired: false, transformFunction: null }, topOffset: { classPropertyName: "topOffset", publicName: "topOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--fold-aside-layout-top": "topOffsetCss()", "class.stack-left-first": "stackLeftFirst()" } }, ngImport: i0, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] });
|
|
4702
4707
|
}
|
|
4703
4708
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAsideLayoutComponent, decorators: [{
|
|
4704
4709
|
type: Component,
|
|
4705
4710
|
args: [{ selector: "fold-aside-layout", standalone: true, host: {
|
|
4706
4711
|
"[style.--fold-aside-layout-top]": "topOffsetCss()",
|
|
4707
4712
|
"[class.stack-left-first]": "stackLeftFirst()",
|
|
4708
|
-
}, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] }]
|
|
4713
|
+
}, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] }]
|
|
4709
4714
|
}], propDecorators: { stackLeftFirst: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackLeftFirst", required: false }] }], asideLeftLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "asideLeftLabel", required: false }] }], asideRightLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "asideRightLabel", required: false }] }], topOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "topOffset", required: false }] }] } });
|
|
4710
4715
|
|
|
4716
|
+
/** DI handle for the nearest {@link FoldNavLayoutComponent}, if any. */
|
|
4717
|
+
const FOLD_NAV_LAYOUT = new InjectionToken("FOLD_NAV_LAYOUT");
|
|
4718
|
+
|
|
4711
4719
|
/**
|
|
4712
4720
|
* Dead band (px) between folding and unfolding. Wider than any scrollbar, so the
|
|
4713
4721
|
* width a fold gives back can never flip the layout straight back.
|
|
4714
4722
|
*/
|
|
4715
4723
|
const HYSTERESIS = 32;
|
|
4716
4724
|
/**
|
|
4717
|
-
* `<fold-
|
|
4725
|
+
* `<fold-nav-layout>` — pairs a tab bar with the content it drives, and owns the
|
|
4718
4726
|
* one thing every tabbed page hand-rolls: where the nav sits.
|
|
4719
4727
|
*
|
|
4720
4728
|
* - `placement="top"` (default) — the nav above the content.
|
|
@@ -4722,39 +4730,62 @@ const HYSTERESIS = 32;
|
|
|
4722
4730
|
* top when the layout gets narrower than {@link foldAt} (so the menu always
|
|
4723
4731
|
* precedes the content it drives, never below it).
|
|
4724
4732
|
*
|
|
4725
|
-
* Content projection — the
|
|
4726
|
-
* - `[tabNav]` → the
|
|
4727
|
-
*
|
|
4733
|
+
* Content projection — the bar stays yours (tabs, active key, events):
|
|
4734
|
+
* - `[tabNav]` → the bar. Project a {@link FoldViewNavComponent} if the tabs
|
|
4735
|
+
* **navigate** (route between views) or a {@link FoldTabsComponent} if they
|
|
4736
|
+
* **switch panels in place** — this layout is agnostic, it only places the bar.
|
|
4737
|
+
* - default slot → the content (routed views, or the tabs' `fold-tab-panel`s).
|
|
4728
4738
|
*
|
|
4729
|
-
* **A11y
|
|
4730
|
-
*
|
|
4731
|
-
*
|
|
4732
|
-
*
|
|
4733
|
-
* roles (the tabs pattern is for panel-switching, not navigation).
|
|
4739
|
+
* **A11y lives in the bar, not here.** `fold-view-nav` brings `<nav>` +
|
|
4740
|
+
* `aria-current`; `fold-tabs` brings the full `role="tablist"` widget with its
|
|
4741
|
+
* `fold-tab-panel`s. This component adds no tab semantics — it only decides where
|
|
4742
|
+
* the bar sits and folds it responsively.
|
|
4734
4743
|
*
|
|
4735
|
-
* A side rail needs a *vertical* bar, a folded one a *horizontal* bar.
|
|
4736
|
-
*
|
|
4737
|
-
*
|
|
4744
|
+
* A side rail needs a *vertical* bar, a folded one a *horizontal* bar. The
|
|
4745
|
+
* projected bar handles that itself with `direction="auto"` (its default) — it
|
|
4746
|
+
* reads this layout through DI ({@link FoldNavLayoutContext}), no wiring:
|
|
4738
4747
|
*
|
|
4739
4748
|
* ```html
|
|
4740
|
-
* <fold-
|
|
4741
|
-
* <fold-
|
|
4742
|
-
*
|
|
4743
|
-
*
|
|
4744
|
-
* [tabs]="tabs"
|
|
4745
|
-
* [activeKey]="tab()"
|
|
4746
|
-
* (tabChange)="tab.set($event)"
|
|
4747
|
-
* />
|
|
4748
|
-
* <app-tab-content />
|
|
4749
|
-
* </fold-tab-layout>
|
|
4749
|
+
* <fold-nav-layout placement="side">
|
|
4750
|
+
* <fold-view-nav tabNav [items]="items" />
|
|
4751
|
+
* <router-outlet />
|
|
4752
|
+
* </fold-nav-layout>
|
|
4750
4753
|
* ```
|
|
4751
4754
|
*
|
|
4752
|
-
*
|
|
4753
|
-
*
|
|
4755
|
+
* The layout also exposes {@link stacked} via `exportAs` for consumers that want
|
|
4756
|
+
* to read the folded state themselves.
|
|
4757
|
+
*
|
|
4758
|
+
* **Two roles — same component, composed differently.**
|
|
4759
|
+
* - **Page scaffold** — the tab rail *is* the page's primary structure: use it
|
|
4760
|
+
* directly (usually `placement="side"`), the folded bar leading the content.
|
|
4761
|
+
* - **Tabbed section** — a tabbed block *inside* a page, among other sections.
|
|
4762
|
+
* Don't reach for a new mode — wrap it in a {@link FoldPageSectionComponent}.
|
|
4763
|
+
* The section owns the title, the `<section>` + heading semantics, the vertical
|
|
4764
|
+
* rhythm and the optional `bleed`; tab-layout stays pure placement. It paints
|
|
4765
|
+
* nothing (no card), so it sits **flat** — add `bleed` on the section for an
|
|
4766
|
+
* edge-to-edge band. The rail folds on the **section's** width (it measures
|
|
4767
|
+
* itself), not the viewport, so nested tabs collapse on their own.
|
|
4754
4768
|
*
|
|
4755
|
-
*
|
|
4769
|
+
* ```html
|
|
4770
|
+
* <!-- a tabbed section: flat structure (page-section) + tab placement -->
|
|
4771
|
+
* <fold-page-section title="Settings" bleed>
|
|
4772
|
+
* <fold-nav-layout placement="side">
|
|
4773
|
+
* <fold-view-nav tabNav [items]="items" />
|
|
4774
|
+
* <app-settings-panel />
|
|
4775
|
+
* </fold-nav-layout>
|
|
4776
|
+
* </fold-page-section>
|
|
4777
|
+
* ```
|
|
4778
|
+
*
|
|
4779
|
+
* Sizing is CSS custom properties: `--fold-nav-layout-gap` (16px) and
|
|
4780
|
+
* `--fold-nav-layout-rail-width` — the side-rail track, defaulting to the shared
|
|
4781
|
+
* `--fold-rail-tertiary` (200px). That's the **tertiary** step of the rail
|
|
4782
|
+
* hierarchy — the same level the `--fold-color-bg-rail-tertiary` colour names
|
|
4783
|
+
* (app menu → workspace → in-page nav) — so every rail in the app stays on one
|
|
4784
|
+
* scale. Override the local var per instance.
|
|
4785
|
+
*
|
|
4786
|
+
* @selector `fold-nav-layout`
|
|
4756
4787
|
*/
|
|
4757
|
-
class
|
|
4788
|
+
class FoldNavLayoutComponent {
|
|
4758
4789
|
/** Where the nav sits: above the content, or as a rail beside it. */
|
|
4759
4790
|
placement = input("top", /* @ts-ignore */
|
|
4760
4791
|
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
@@ -4795,14 +4826,40 @@ class FoldTabLayoutComponent {
|
|
|
4795
4826
|
this.folded.set(false);
|
|
4796
4827
|
}
|
|
4797
4828
|
}
|
|
4798
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type:
|
|
4799
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type:
|
|
4829
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4830
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldNavLayoutComponent, isStandalone: true, selector: "fold-nav-layout", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, foldAt: { classPropertyName: "foldAt", publicName: "foldAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-row": "!stacked()" } }, providers: [
|
|
4831
|
+
{ provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
|
|
4832
|
+
], exportAs: ["foldNavLayout"], ngImport: i0, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] });
|
|
4800
4833
|
}
|
|
4801
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type:
|
|
4834
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, decorators: [{
|
|
4802
4835
|
type: Component,
|
|
4803
|
-
args: [{ selector: "fold-
|
|
4836
|
+
args: [{ selector: "fold-nav-layout", standalone: true, exportAs: "foldNavLayout", host: { "[class.is-row]": "!stacked()" }, providers: [
|
|
4837
|
+
{ provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
|
|
4838
|
+
], template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] }]
|
|
4804
4839
|
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], foldAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "foldAt", required: false }] }] } });
|
|
4805
4840
|
|
|
4841
|
+
/**
|
|
4842
|
+
* Marks a custom page title projected into {@link FoldPageLayoutComponent} — put
|
|
4843
|
+
* it on inline heading content when the plain `icon` + `title` inputs aren't
|
|
4844
|
+
* enough (an avatar, a two-tone title, a live status). It renders inside the
|
|
4845
|
+
* page's `<h1>`, so keep it inline and text-like. Its presence switches the
|
|
4846
|
+
* header on in place of the input-driven title.
|
|
4847
|
+
*
|
|
4848
|
+
* @example
|
|
4849
|
+
* ```html
|
|
4850
|
+
* <fold-page-layout>
|
|
4851
|
+
* <span pageTitle><fold-avatar … /> Acme Records</span>
|
|
4852
|
+
* </fold-page-layout>
|
|
4853
|
+
* ```
|
|
4854
|
+
*/
|
|
4855
|
+
class FoldPageTitleDirective {
|
|
4856
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4857
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldPageTitleDirective, isStandalone: true, selector: "[pageTitle]", ngImport: i0 });
|
|
4858
|
+
}
|
|
4859
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageTitleDirective, decorators: [{
|
|
4860
|
+
type: Directive,
|
|
4861
|
+
args: [{ selector: "[pageTitle]", standalone: true }]
|
|
4862
|
+
}] });
|
|
4806
4863
|
/**
|
|
4807
4864
|
* `<fold-page-layout>` — the vertical scaffold for a settings/admin-style page:
|
|
4808
4865
|
* an optional `icon` + `title` header with a description slot, an optional
|
|
@@ -4824,6 +4881,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4824
4881
|
*
|
|
4825
4882
|
* Content projection:
|
|
4826
4883
|
* - default slot → the page body (sections, cards, banners…).
|
|
4884
|
+
* - `[pageTitle]` ({@link FoldPageTitleDirective}) → a custom title, rendered
|
|
4885
|
+
* inside the `<h1>` in place of the `icon` + `title` inputs, for when a plain
|
|
4886
|
+
* string won't do (an avatar, a two-tone title). Its presence alone switches
|
|
4887
|
+
* the header on.
|
|
4827
4888
|
* - `[titleBadge]` → an inline pill beside the title (e.g. a status/kind badge).
|
|
4828
4889
|
* - `p[description]` → the intro under the title. A **slot**, not a string
|
|
4829
4890
|
* input: a description that needs a `<code>`, a link or a second sentence is
|
|
@@ -4854,13 +4915,17 @@ class FoldPageLayoutComponent {
|
|
|
4854
4915
|
/** An optional leading icon shown beside the title. */
|
|
4855
4916
|
icon = input(/* @ts-ignore */
|
|
4856
4917
|
...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
4918
|
+
/** A projected `[pageTitle]`, if any — switches the header on for a custom
|
|
4919
|
+
* title even without the `title` input. */
|
|
4920
|
+
customTitle = contentChild(FoldPageTitleDirective, /* @ts-ignore */
|
|
4921
|
+
...(ngDevMode ? [{ debugName: "customTitle" }] : /* istanbul ignore next */ []));
|
|
4857
4922
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4858
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldPageLayoutComponent, isStandalone: true, selector: "fold-page-layout", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (title()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n
|
|
4923
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldPageLayoutComponent, isStandalone: true, selector: "fold-page-layout", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "customTitle", first: true, predicate: FoldPageTitleDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@if (title() || customTitle()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n <h1 class=\"page-title\">\n @if (title(); as t) {\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ t }}</span>\n }\n <ng-content select=\"[pageTitle]\" />\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
|
|
4859
4924
|
}
|
|
4860
4925
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, decorators: [{
|
|
4861
4926
|
type: Component,
|
|
4862
|
-
args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], template: "@if (title()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n
|
|
4863
|
-
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
|
|
4927
|
+
args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], template: "@if (title() || customTitle()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n <h1 class=\"page-title\">\n @if (title(); as t) {\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ t }}</span>\n }\n <ng-content select=\"[pageTitle]\" />\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"] }]
|
|
4928
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], customTitle: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldPageTitleDirective), { isSignal: true }] }] } });
|
|
4864
4929
|
|
|
4865
4930
|
/**
|
|
4866
4931
|
* `<fold-page-section>` — a titled, semantic **`<section>`** grouping of page
|
|
@@ -5128,64 +5193,285 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
5128
5193
|
}], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: true }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }] } });
|
|
5129
5194
|
|
|
5130
5195
|
/**
|
|
5131
|
-
* `<fold-
|
|
5132
|
-
*
|
|
5196
|
+
* `<fold-view-nav>` — a **navigation** bar styled as tabs: a `<nav>` whose items
|
|
5197
|
+
* *go somewhere*. Give each item a `link` (or `href`) and it renders a real
|
|
5198
|
+
* `<a>` — so cmd/middle-click opens a new tab, the URL is a deep link, and the
|
|
5199
|
+
* active item is driven automatically by `routerLinkActive` +
|
|
5200
|
+
* `aria-current="page"`. Without a link an item is a `<button>` that emits
|
|
5201
|
+
* `activeChange` and reads its active state from `activeKey` (view switching
|
|
5202
|
+
* with no route).
|
|
5133
5203
|
*
|
|
5134
|
-
*
|
|
5135
|
-
*
|
|
5136
|
-
*
|
|
5137
|
-
*
|
|
5204
|
+
* **Navigation, not the tabs widget.** Reach for this when a tab routes / switches
|
|
5205
|
+
* views. When tabs toggle **layered panels in place** (same URL), use
|
|
5206
|
+
* {@link FoldTabsComponent} — same look, but the ARIA Tabs pattern
|
|
5207
|
+
* (`role="tablist"`, arrow keys, `tabpanel`).
|
|
5138
5208
|
*
|
|
5139
|
-
*
|
|
5209
|
+
* - `activeStyle` — `underline` (accent border) or `fill` (accent pill).
|
|
5210
|
+
* - `direction` — `auto` (default: follows a wrapping `fold-nav-layout`, else
|
|
5211
|
+
* vertical), `vertical`, or `horizontal`.
|
|
5212
|
+
*
|
|
5213
|
+
* @selector `fold-view-nav`
|
|
5140
5214
|
*
|
|
5141
5215
|
* @example
|
|
5142
5216
|
* ```html
|
|
5143
|
-
*
|
|
5144
|
-
*
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5147
|
-
*
|
|
5148
|
-
*
|
|
5149
|
-
* />
|
|
5217
|
+
* <!-- real navigation: link items, active state automatic -->
|
|
5218
|
+
* <fold-view-nav [items]="[
|
|
5219
|
+
* { key: 'members', label: 'Members', link: 'members', badge: 3 },
|
|
5220
|
+
* { key: 'settings', label: 'Settings', link: 'settings' },
|
|
5221
|
+
* ]" />
|
|
5222
|
+
* <router-outlet />
|
|
5150
5223
|
* ```
|
|
5151
5224
|
*/
|
|
5152
|
-
class
|
|
5225
|
+
class FoldViewNavComponent {
|
|
5226
|
+
/** The items to render, in order. */
|
|
5227
|
+
items = input.required(/* @ts-ignore */
|
|
5228
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
5229
|
+
/**
|
|
5230
|
+
* The active item's `key` — for **button** items (no `link`). Link items ignore
|
|
5231
|
+
* it: their active state comes from the URL via `routerLinkActive`.
|
|
5232
|
+
*/
|
|
5233
|
+
activeKey = input("", /* @ts-ignore */
|
|
5234
|
+
...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
|
|
5235
|
+
/** How the active item reads: accent underline, or accent fill. */
|
|
5236
|
+
activeStyle = input("underline", /* @ts-ignore */
|
|
5237
|
+
...(ngDevMode ? [{ debugName: "activeStyle" }] : /* istanbul ignore next */ []));
|
|
5238
|
+
/**
|
|
5239
|
+
* Bar orientation. Defaults to `auto`: inside a `fold-nav-layout` it follows
|
|
5240
|
+
* the layout (vertical rail, horizontal once folded) with no wiring; on its
|
|
5241
|
+
* own it is `vertical` — the readable-first shape, reading as the app's
|
|
5242
|
+
* **third navigation rail** (app menu → workspace → in-page views, the
|
|
5243
|
+
* `--fold-rail-tertiary` level). Force `horizontal` for a page-level top bar.
|
|
5244
|
+
*/
|
|
5245
|
+
direction = input("auto", /* @ts-ignore */
|
|
5246
|
+
...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
|
|
5247
|
+
/**
|
|
5248
|
+
* Density — pure padding/typography:
|
|
5249
|
+
* - `compact` (default) — inline / popover bars.
|
|
5250
|
+
* - `comfortable` — a prominent, page-level bar.
|
|
5251
|
+
*/
|
|
5252
|
+
size = input("compact", /* @ts-ignore */
|
|
5253
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5254
|
+
/**
|
|
5255
|
+
* Collapse the bar to icons (independent of {@link size}). **Horizontal**, an
|
|
5256
|
+
* icon accordion: every item but the active one drops to its icon, the active
|
|
5257
|
+
* one keeps its label. **Vertical**, a collapsed icon rail (like a folded
|
|
5258
|
+
* menu): every item shows just its icon, its label a hover/focus tooltip and
|
|
5259
|
+
* its count a corner bubble — narrow the layout's `--fold-nav-layout-rail-width`
|
|
5260
|
+
* to match. An item with no icon falls back to a square glyph of its label's
|
|
5261
|
+
* initial, so the rail stays icon-width whether or not items carry icons.
|
|
5262
|
+
* Toggle it from your own breakpoint when the bar is too tight.
|
|
5263
|
+
*/
|
|
5264
|
+
collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5265
|
+
/**
|
|
5266
|
+
* `transparent` (default — blends with the app, so the items read directly) or
|
|
5267
|
+
* `surface` (a filled bar that carries its own rail background).
|
|
5268
|
+
*/
|
|
5269
|
+
background = input("transparent", /* @ts-ignore */
|
|
5270
|
+
...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
|
|
5271
|
+
/** Emits the `key` of a clicked **button** item (link items just navigate). */
|
|
5272
|
+
activeChange = output();
|
|
5273
|
+
/** The nearest layout, if any — lets `direction="auto"` follow it. */
|
|
5274
|
+
layout = inject(FOLD_NAV_LAYOUT, { optional: true });
|
|
5275
|
+
/** `direction` with `auto` resolved against the wrapping layout. */
|
|
5276
|
+
resolvedDirection = computed(() => {
|
|
5277
|
+
const d = this.direction();
|
|
5278
|
+
if (d !== "auto") {
|
|
5279
|
+
return d;
|
|
5280
|
+
}
|
|
5281
|
+
return this.layout?.stacked() ? "horizontal" : "vertical";
|
|
5282
|
+
}, /* @ts-ignore */
|
|
5283
|
+
...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
|
|
5284
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5285
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldViewNavComponent, isStandalone: true, selector: "fold-view-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeChange: "activeChange" }, ngImport: i0, template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeChange.emit(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
|
|
5286
|
+
}
|
|
5287
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, decorators: [{
|
|
5288
|
+
type: Component,
|
|
5289
|
+
args: [{ selector: "fold-view-nav", standalone: true, imports: [
|
|
5290
|
+
NgTemplateOutlet,
|
|
5291
|
+
FoldIconComponent,
|
|
5292
|
+
FoldBadgeComponent,
|
|
5293
|
+
RouterLink,
|
|
5294
|
+
RouterLinkActive,
|
|
5295
|
+
], template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeChange.emit(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"] }]
|
|
5296
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], activeChange: [{ type: i0.Output, args: ["activeChange"] }] } });
|
|
5297
|
+
|
|
5298
|
+
/**
|
|
5299
|
+
* `<fold-tabs>` — the **in-page tabs widget** (the ARIA Tabs pattern): a
|
|
5300
|
+
* `role="tablist"` of `role="tab"` buttons that switch layered panels **without
|
|
5301
|
+
* navigating**. Arrow keys move between tabs (roving tabindex — only the active
|
|
5302
|
+
* tab is in the Tab order), `Home`/`End` jump to the ends, and each tab is wired
|
|
5303
|
+
* to its panel via `aria-controls` ↔ `aria-labelledby`.
|
|
5304
|
+
*
|
|
5305
|
+
* Pair it with one {@link FoldTabPanelComponent} per key, handed the bar via a
|
|
5306
|
+
* template ref so the two coordinate across slots (e.g. inside a
|
|
5307
|
+
* `fold-nav-layout`). Same visual as {@link FoldViewNavComponent}; the
|
|
5308
|
+
* difference is **semantics** — use `fold-tabs` for in-page panel switching, and
|
|
5309
|
+
* `fold-view-nav` when the tabs actually navigate (route) between views.
|
|
5310
|
+
*
|
|
5311
|
+
* ```html
|
|
5312
|
+
* <fold-nav-layout>
|
|
5313
|
+
* <fold-tabs
|
|
5314
|
+
* nav
|
|
5315
|
+
* #t="foldTabs"
|
|
5316
|
+
* [tabs]="tabs"
|
|
5317
|
+
* [activeKey]="tab()"
|
|
5318
|
+
* (tabChange)="tab.set($event)"
|
|
5319
|
+
* />
|
|
5320
|
+
* <fold-tab-panel [tabs]="t" key="overview">…</fold-tab-panel>
|
|
5321
|
+
* <fold-tab-panel [tabs]="t" key="settings">…</fold-tab-panel>
|
|
5322
|
+
* </fold-nav-layout>
|
|
5323
|
+
* ```
|
|
5324
|
+
*
|
|
5325
|
+
* Same visual knobs as `fold-view-nav` (`activeStyle` · `direction` · `size` ·
|
|
5326
|
+
* `background`). Sizing shares the tab-bar tokens.
|
|
5327
|
+
*
|
|
5328
|
+
* @selector `fold-tabs`
|
|
5329
|
+
*/
|
|
5330
|
+
class FoldTabsComponent {
|
|
5153
5331
|
/** The tabs to render, in order. */
|
|
5154
5332
|
tabs = input.required(/* @ts-ignore */
|
|
5155
5333
|
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
5156
|
-
/** The `key` of the
|
|
5334
|
+
/** The `key` of the active tab (the shown panel). */
|
|
5157
5335
|
activeKey = input.required(/* @ts-ignore */
|
|
5158
5336
|
...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
|
|
5159
5337
|
/** How the active tab reads: accent underline, or accent fill. */
|
|
5160
5338
|
activeStyle = input("underline", /* @ts-ignore */
|
|
5161
5339
|
...(ngDevMode ? [{ debugName: "activeStyle" }] : /* istanbul ignore next */ []));
|
|
5162
|
-
/** Bar orientation (vertical auto-collapses to horizontal on mobile). */
|
|
5163
|
-
direction = input("horizontal", /* @ts-ignore */
|
|
5164
|
-
...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
|
|
5165
5340
|
/**
|
|
5166
|
-
*
|
|
5167
|
-
*
|
|
5168
|
-
* (and badge) to just its icon, while the active tab keeps its label and
|
|
5169
|
-
* takes the remaining room. A tab with no icon keeps its label, so nothing
|
|
5170
|
-
* is left unlabelled. Swap to it from your own breakpoint when the bar is
|
|
5171
|
-
* too tight for every label.
|
|
5172
|
-
* - `compact` (default) — inline / popover bars.
|
|
5173
|
-
* - `comfortable` — a prominent, page-level bar.
|
|
5341
|
+
* Bar orientation (drives `aria-orientation`). Defaults to `auto`: follows a
|
|
5342
|
+
* wrapping `fold-nav-layout`, else `horizontal`.
|
|
5174
5343
|
*/
|
|
5344
|
+
direction = input("auto", /* @ts-ignore */
|
|
5345
|
+
...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
|
|
5346
|
+
/** Density — see {@link FoldViewNavComponent} for the same scale. */
|
|
5175
5347
|
size = input("compact", /* @ts-ignore */
|
|
5176
5348
|
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5177
|
-
/**
|
|
5349
|
+
/** Collapse to icons — see {@link FoldViewNavComponent.collapsed}. */
|
|
5350
|
+
collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5351
|
+
/** `surface` (filled bar, default) or `transparent`. */
|
|
5178
5352
|
background = input("surface", /* @ts-ignore */
|
|
5179
5353
|
...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
|
|
5180
|
-
/** Emits the `key
|
|
5354
|
+
/** Emits the newly-selected tab's `key`. */
|
|
5181
5355
|
tabChange = output();
|
|
5182
|
-
|
|
5183
|
-
|
|
5356
|
+
/** The nearest layout, if any — lets `direction="auto"` follow it. */
|
|
5357
|
+
layout = inject(FOLD_NAV_LAYOUT, { optional: true });
|
|
5358
|
+
/** `direction` with `auto` resolved against the wrapping layout. */
|
|
5359
|
+
resolvedDirection = computed(() => {
|
|
5360
|
+
const d = this.direction();
|
|
5361
|
+
if (d !== "auto") {
|
|
5362
|
+
return d;
|
|
5363
|
+
}
|
|
5364
|
+
return this.layout?.stacked() ? "horizontal" : "vertical";
|
|
5365
|
+
}, /* @ts-ignore */
|
|
5366
|
+
...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
|
|
5367
|
+
/** Per-instance id root, so ids are unique + stable across SSR. */
|
|
5368
|
+
uid = inject(FoldIdService).next("fold-tabs");
|
|
5369
|
+
tabButtons = viewChildren("tabBtn", /* @ts-ignore */
|
|
5370
|
+
...(ngDevMode ? [{ debugName: "tabButtons" }] : /* istanbul ignore next */ []));
|
|
5371
|
+
tabId(key) {
|
|
5372
|
+
return `${this.uid}-tab-${key}`;
|
|
5373
|
+
}
|
|
5374
|
+
panelId(key) {
|
|
5375
|
+
return `${this.uid}-panel-${key}`;
|
|
5376
|
+
}
|
|
5377
|
+
select(key) {
|
|
5378
|
+
this.tabChange.emit(key);
|
|
5379
|
+
}
|
|
5380
|
+
/**
|
|
5381
|
+
* Roving-tabindex keyboard model (APG Tabs, automatic activation): arrows move
|
|
5382
|
+
* — and select — the adjacent tab, `Home`/`End` the ends. Both axes work, so
|
|
5383
|
+
* it stays correct whether the bar is horizontal, vertical, or a
|
|
5384
|
+
* mobile-collapsed vertical.
|
|
5385
|
+
*/
|
|
5386
|
+
onKeydown(event) {
|
|
5387
|
+
const keys = this.tabs().map((t) => t.key);
|
|
5388
|
+
if (keys.length === 0) {
|
|
5389
|
+
return;
|
|
5390
|
+
}
|
|
5391
|
+
const current = keys.indexOf(this.activeKey());
|
|
5392
|
+
let target;
|
|
5393
|
+
switch (event.key) {
|
|
5394
|
+
case "ArrowRight":
|
|
5395
|
+
case "ArrowDown":
|
|
5396
|
+
target = (current + 1) % keys.length;
|
|
5397
|
+
break;
|
|
5398
|
+
case "ArrowLeft":
|
|
5399
|
+
case "ArrowUp":
|
|
5400
|
+
target = (current - 1 + keys.length) % keys.length;
|
|
5401
|
+
break;
|
|
5402
|
+
case "Home":
|
|
5403
|
+
target = 0;
|
|
5404
|
+
break;
|
|
5405
|
+
case "End":
|
|
5406
|
+
target = keys.length - 1;
|
|
5407
|
+
break;
|
|
5408
|
+
default:
|
|
5409
|
+
return;
|
|
5410
|
+
}
|
|
5411
|
+
event.preventDefault();
|
|
5412
|
+
const key = keys[target];
|
|
5413
|
+
if (key === undefined) {
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
this.tabChange.emit(key);
|
|
5417
|
+
this.tabButtons()[target]?.nativeElement.focus();
|
|
5418
|
+
}
|
|
5419
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5420
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldTabsComponent, isStandalone: true, selector: "fold-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: true, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange" }, viewQueries: [{ propertyName: "tabButtons", predicate: ["tabBtn"], descendants: true, isSignal: true }], exportAs: ["foldTabs"], ngImport: i0, template: "<div\n class=\"tab-bar\"\n role=\"tablist\"\n [attr.aria-orientation]=\"\n resolvedDirection() === 'vertical' ? 'vertical' : 'horizontal'\n \"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n (keydown)=\"onKeydown($event)\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"tab-bar-item\"\n [id]=\"tabId(tab.key)\"\n [class.is-active]=\"activeKey() === tab.key\"\n [attr.aria-selected]=\"activeKey() === tab.key\"\n [attr.aria-controls]=\"panelId(tab.key)\"\n [tabindex]=\"activeKey() === tab.key ? 0 : -1\"\n (click)=\"select(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n tab.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }] });
|
|
5421
|
+
}
|
|
5422
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabsComponent, decorators: [{
|
|
5423
|
+
type: Component,
|
|
5424
|
+
args: [{ selector: "fold-tabs", standalone: true, exportAs: "foldTabs", imports: [FoldIconComponent, FoldBadgeComponent], template: "<div\n class=\"tab-bar\"\n role=\"tablist\"\n [attr.aria-orientation]=\"\n resolvedDirection() === 'vertical' ? 'vertical' : 'horizontal'\n \"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n (keydown)=\"onKeydown($event)\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"tab-bar-item\"\n [id]=\"tabId(tab.key)\"\n [class.is-active]=\"activeKey() === tab.key\"\n [attr.aria-selected]=\"activeKey() === tab.key\"\n [attr.aria-controls]=\"panelId(tab.key)\"\n [tabindex]=\"activeKey() === tab.key ? 0 : -1\"\n (click)=\"select(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n tab.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"] }]
|
|
5425
|
+
}], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: true }] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], tabButtons: [{ type: i0.ViewChildren, args: ["tabBtn", { isSignal: true }] }] } });
|
|
5426
|
+
|
|
5427
|
+
/**
|
|
5428
|
+
* `<fold-tab-panel>` — one panel of a {@link FoldTabsComponent}. Handed the bar
|
|
5429
|
+
* via a template ref (`[tabs]="t"`, where `#t="foldTabs"`) plus its `key`, it
|
|
5430
|
+
* becomes a `role="tabpanel"` named by its tab (`aria-labelledby`), reachable by
|
|
5431
|
+
* `aria-controls` from that tab, focusable while active, and `hidden` otherwise.
|
|
5432
|
+
*
|
|
5433
|
+
* The ref pairing is what lets the tabs bar and its panels sit in **different**
|
|
5434
|
+
* slots (e.g. the bar in a `fold-nav-layout`'s nav slot, the panels in its
|
|
5435
|
+
* content) and still coordinate.
|
|
5436
|
+
*
|
|
5437
|
+
* ```html
|
|
5438
|
+
* <fold-tabs #t="foldTabs" [tabs]="tabs" [activeKey]="tab()" (tabChange)="tab.set($event)" />
|
|
5439
|
+
* <fold-tab-panel [tabs]="t" key="overview">…</fold-tab-panel>
|
|
5440
|
+
* ```
|
|
5441
|
+
*
|
|
5442
|
+
* @selector `fold-tab-panel`
|
|
5443
|
+
*/
|
|
5444
|
+
class FoldTabPanelComponent {
|
|
5445
|
+
/** The owning `fold-tabs`, read via its template ref (`#t="foldTabs"`). */
|
|
5446
|
+
tabs = input.required(/* @ts-ignore */
|
|
5447
|
+
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
5448
|
+
/** This panel's tab key — must match one tab in the bar. */
|
|
5449
|
+
key = input.required(/* @ts-ignore */
|
|
5450
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
5451
|
+
isActive = computed(() => this.tabs().activeKey() === this.key(), /* @ts-ignore */
|
|
5452
|
+
...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
|
|
5453
|
+
panelId = computed(() => this.tabs().panelId(this.key()), /* @ts-ignore */
|
|
5454
|
+
...(ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []));
|
|
5455
|
+
labelledBy = computed(() => this.tabs().tabId(this.key()), /* @ts-ignore */
|
|
5456
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
5457
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5458
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldTabPanelComponent, isStandalone: true, selector: "fold-tab-panel", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tabpanel" }, properties: { "id": "panelId()", "attr.aria-labelledby": "labelledBy()", "attr.tabindex": "isActive() ? 0 : null", "hidden": "!isActive()" } }, ngImport: i0, template: "<ng-content />", isInline: true });
|
|
5184
5459
|
}
|
|
5185
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type:
|
|
5460
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabPanelComponent, decorators: [{
|
|
5186
5461
|
type: Component,
|
|
5187
|
-
args: [{
|
|
5188
|
-
|
|
5462
|
+
args: [{
|
|
5463
|
+
selector: "fold-tab-panel",
|
|
5464
|
+
standalone: true,
|
|
5465
|
+
host: {
|
|
5466
|
+
role: "tabpanel",
|
|
5467
|
+
"[id]": "panelId()",
|
|
5468
|
+
"[attr.aria-labelledby]": "labelledBy()",
|
|
5469
|
+
"[attr.tabindex]": "isActive() ? 0 : null",
|
|
5470
|
+
"[hidden]": "!isActive()",
|
|
5471
|
+
},
|
|
5472
|
+
template: "<ng-content />",
|
|
5473
|
+
}]
|
|
5474
|
+
}], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }] } });
|
|
5189
5475
|
|
|
5190
5476
|
/** Variant → leading status icon. */
|
|
5191
5477
|
const VARIANT_ICON = {
|
|
@@ -5816,5 +6102,5 @@ class FoldPanelToggle {
|
|
|
5816
6102
|
* Generated bundle index. Do not edit.
|
|
5817
6103
|
*/
|
|
5818
6104
|
|
|
5819
|
-
export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_MOTION_TOKENS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalloutComponent, FoldCardComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDisclosureComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInputComponent, FoldLinkComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldNavLauncherComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective,
|
|
6105
|
+
export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_MOTION_TOKENS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalloutComponent, FoldCardComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDisclosureComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInputComponent, FoldLinkComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldNavLauncherComponent, FoldNavLayoutComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPageTitleDirective, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective, FoldTabPanelComponent, FoldTabsComponent, FoldTimelineComponent, FoldToastComponent, FoldToastContainerComponent, FoldToastService, FoldToggleIconComponent, FoldViewNavComponent, foldBlurVar, foldColorProperty, foldColorVar, foldHashSeed, foldMotionVar, foldRadiusVar, foldResolvePalette, foldShadowVar, foldSpaceVar, foldTextVar, observeElementWidth, provideFoldIcons, provideFoldPalette, provideFoldPanelLabels, provideFoldToasts };
|
|
5820
6106
|
//# sourceMappingURL=fold-ng.mjs.map
|