fold-ng 0.2.0 → 0.2.1

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 CHANGED
@@ -8,6 +8,18 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.2.1] - 2026-07-24
12
+
13
+ ### Fixed
14
+
15
+ - **`fold-app-shell`** now sets the drawer's background `inert` via an attribute
16
+ binding (`[attr.inert]`) rather than a property binding. No behaviour change —
17
+ AOT was always clean — but it silences an `NG0303` "unknown property" warning
18
+ raised by the JIT element-schema in dev/test runtimes.
19
+ - **Published tarball** no longer ships `tsconfig.lib.tsbuildinfo` — a ~100 kB
20
+ TypeScript incremental-build cache that `finalize-dist` now prunes. Smaller
21
+ install (14 → 13 files).
22
+
11
23
  ## [0.2.0] - 2026-07-24
12
24
 
13
25
  ### Added
@@ -64,6 +76,7 @@ design-token stylesheet.
64
76
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
65
77
  strings localise via inputs / providers (`provideFoldPanelLabels`).
66
78
 
67
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.2.0...HEAD
79
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.2.1...HEAD
80
+ [0.2.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.1
68
81
  [0.2.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.0
69
82
  [0.1.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.1.0
package/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/fold-ng.svg)](https://www.npmjs.com/package/fold-ng)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/fold-ng.svg)](https://www.npmjs.com/package/fold-ng)
5
- [![minzipped size](https://img.shields.io/bundlephobia/minzip/fold-ng)](https://bundlephobia.com/package/fold-ng)
5
+ [![install size](https://packagephobia.com/badge?p=fold-ng)](https://packagephobia.com/result?p=fold-ng)
6
6
  [![types included](https://img.shields.io/npm/types/fold-ng.svg)](https://www.npmjs.com/package/fold-ng)
7
7
  [![license](https://img.shields.io/npm/l/fold-ng.svg)](./LICENSE)
8
8
  [![status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-2ea043)](./CHANGELOG.md)
9
9
  [![live demo](https://img.shields.io/badge/live_demo-gallery-6f42c1)](https://hugoheynard.github.io/fold-ng/)
10
+ [![Sponsor](https://img.shields.io/github/sponsors/hugoheynard?logo=githubsponsors&label=Sponsor&color=ea4aaa)](https://github.com/sponsors/hugoheynard)
10
11
 
11
12
  **fold-ng** is an accessible, **dark-first Angular 22 UI component library and
12
13
  design system** — signals-first, standalone, zoneless, and SSR-ready. It ships a
@@ -35,6 +36,12 @@ npm install fold-ng
35
36
  Angular 22 (`@angular/core`, `@angular/common`, `@angular/forms`,
36
37
  `@angular/platform-browser`) is a peer dependency.
37
38
 
39
+ > **Bundle size.** Components are standalone and the package is side-effect-free
40
+ > except its CSS (`sideEffects: ["**/*.css"]`), so you only ship what you import —
41
+ > not the whole library. (No Bundlephobia badge: it can't measure an Angular
42
+ > partial-Ivy package, which needs the Angular linker to become final JS — the
43
+ > install-size badge above is the honest figure.)
44
+
38
45
  ## Quickstart
39
46
 
40
47
  Import the tokens once, then use any standalone component directly:
@@ -488,7 +488,7 @@ class FoldAppShellComponent {
488
488
  headerHeightMobileVar = computed(() => pxVar(this.headerHeightMobile()), /* @ts-ignore */
489
489
  ...(ngDevMode ? [{ debugName: "headerHeightMobileVar" }] : /* istanbul ignore next */ []));
490
490
  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"] }] });
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\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
492
  }
493
493
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAppShellComponent, decorators: [{
494
494
  type: Component,
@@ -502,7 +502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
502
502
  "[class.mobile-drawer]": 'mobileNav() === "drawer"',
503
503
  "[class.mobile-nav-open]": "drawerOpen()",
504
504
  "[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 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"] }]
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\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
506
  }], 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
507
  type: HostListener,
508
508
  args: ["document:keydown.escape"]