UNPKG

@blackbaud/skyux-lib-stache

Version:

This library was generated with [Nx](https://nx.dev).

1,022 lines (1,002 loc) 186 kB
import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, InjectionToken, Optional, Inject, NgModule, HostListener, HostBinding, Input, Directive, Component, inject, RendererFactory2, ElementRef, ViewChild, CSP_NONCE, ChangeDetectionStrategy, TemplateRef, ViewEncapsulation } from '@angular/core'; import * as i1$1 from '@skyux/layout'; import { SkyActionButtonModule, SkyFluidGridModule } from '@skyux/layout'; import * as i2 from '@skyux/lookup'; import { SkySearchModule } from '@skyux/lookup'; import * as i1 from '@angular/router'; import { NavigationStart, ROUTES, Router, RouterModule, NavigationEnd } from '@angular/router'; import { of, Subject, fromEvent, ReplaySubject, BehaviorSubject } from 'rxjs'; import { takeUntil, first, map, pairwise, take } from 'rxjs/operators'; import * as i2$1 from '@skyux/i18n'; import { SkyLibResourcesService, SkyI18nModule } from '@skyux/i18n'; import * as i1$2 from '@skyux/config'; import { SkyAppConfig } from '@skyux/config'; import * as i3$1 from '@skyux/icon'; import { SkyIconModule } from '@skyux/icon'; import lodashGet from 'lodash.get'; import * as i2$2 from '@skyux/core'; import { SkyMediaBreakpoints } from '@skyux/core'; import { Title } from '@angular/platform-browser'; class StacheAuthService { constructor() { this.isAuthenticated = of(false); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAuthService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAuthService, decorators: [{ type: Injectable }] }); const stringConverter = (value) => { if (value === undefined || typeof value === 'string') { return value; } return value.toString(); }; const booleanConverter = (value) => { if (value === undefined || typeof value === 'boolean') { return value; } return value.toString() === 'true'; }; const numberConverter = (value) => { if (value === undefined || typeof value === 'number') { return value; } return parseFloat(value.toString()); }; const STACHE_ROUTE_OPTIONS = new InjectionToken('StacheRouteOptions'); function sortByName(a, b) { if (a.name.toLowerCase() < b.name.toLowerCase()) { return -1; } else if (a.name.toLowerCase() > b.name.toLowerCase()) { return 1; } else { return 0; } } function sortByOrder(a, b) { /*istanbul ignore if: this function is never called when order is undefined*/ if (a.order === undefined || b.order === undefined) { return -1; } if (a.order < b.order) { return -1; } else if (a.order > b.order) { return 1; } else { return 0; } } function clone(thing) { return JSON.parse(JSON.stringify(thing)); } /** * The StacheRouteService assumes the consuming application's routes are "flat", without children. * For example: * ``` * const routes: Routes = [ * { * path: '', * component: HomeComponent, * }, * { * path: 'design', * component: DesignPlaygroundComponent, * data: { * stache: { * name: 'Design', * }, * }, * }, * { * path: 'design/styles', * component: StylesPlaygroundComponent, * data: { * stache: { * name: 'Styles', * }, * }, * }, * ]; * ``` * Import the StacheRouterModule into lazy-loaded modules to give the Stache route service the desired route config. * For example: * ``` * @NgModule({ * import: [ * RouterModule.forChild({...}), * StacheRouterModule.forChild('my-top-level-route') * ] * }) * export class MyLazyLoadedModule {} * ``` * Lazy loaded components will lose the context provided by StacheRouterModule.forChild(...) * You may override the StacheRouteOptions via a route provider to wire lazy components into navigation * ``` * const routes: Routes = [ * { * path: 'lazy', * loadComponent: () => import('./path/to/lazy.component'), * providers: [ * { provide: STACHE_ROUTE_OPTIONS, useValue: {basePath: 'nested'}} * ] * data: { * stache: { * name: 'Lazy', * }, * }, * }, * ]; * ``` */ class StacheRouteService { #activeRoutes; #ngUnsubscribe = new Subject(); #options; #router; #routes = []; constructor(router, routes, options) { this.#options = options; this.#router = router; this.#routes = [].concat(...(routes || [])); router.events.pipe(takeUntil(this.#ngUnsubscribe)).subscribe((val) => { if (val instanceof NavigationStart) { this.clearActiveRoutes(); } }); } ngOnDestroy() { this.#ngUnsubscribe.next(); this.#ngUnsubscribe.complete(); } getActiveRoutes() { if (this.#activeRoutes) { return this.#activeRoutes; } const rootPath = this.getActiveUrl().replace(/^\//, '').split('/')[0]; const appRoutes = this.#options?.basePath ? this.#routes : this.#getRouteBranch(this.#routes, rootPath); const activeChildRoutes = appRoutes .filter((route) => { // If options.path is specified, it means that all routes are children // of the root path. return this.#options?.basePath || route.path?.indexOf(rootPath) === 0; }) .map((route) => { const path = this.#prependOptionsPath(route.path); return { path, segments: path?.split('/'), data: route.data, }; }); const activeRoutes = [ { path: rootPath, segments: [rootPath], children: this.#assignChildren(activeChildRoutes, rootPath), data: activeChildRoutes.find((route) => route.path === rootPath)?.data, }, ]; this.#activeRoutes = this.#formatRoutes(activeRoutes); return clone(this.#activeRoutes); } getActiveUrl() { return this.#router.url.split('?')[0].split('#')[0]; } clearActiveRoutes() { this.#activeRoutes = undefined; } /** * Recursively finds the "branch" of the routes tree that contains the given root path. */ #getRouteBranch(routes, rootPath) { const pathExistsInRoot = routes.some((route) => route.path?.indexOf(rootPath) === 0); if (pathExistsInRoot) { return routes; } // Path not found, look in each route's children. for (const route of routes) { if (route.children) { const childRoutes = this.#getRouteBranch(route.children, rootPath); if (childRoutes.length > 0) { return childRoutes; } } } return []; } #prependOptionsPath(path) { // If options.path is specified, all routes are children of the root path, // so prepend it to each path when building the path for navigation. if (this.#options?.basePath) { path = path ? `${this.#options.basePath}/${path}` : this.#options.basePath; } return path; } #assignChildren(routes, parentPath) { const assignedRoutes = []; const depth = parentPath.split('/').length + 1; routes.forEach((route) => { const routeDepth = route.segments.length; // Adding trailing slash to force end of parent path. Otherwise: // a/child, a1/child, and a2/child would have all three children displayed under a. const isChildRoute = depth === routeDepth && route.path.indexOf(parentPath + '/') > -1; if (isChildRoute) { route.children = this.#assignChildren(routes, route.path); assignedRoutes.push(route); } }); return assignedRoutes; } #formatRoutes(routes) { const formatted = routes .map((route) => { const pathMetadata = this.#validateNavOrder({ ...route.data?.['stache'], showInNav: (route.data?.['stache']?.showInNav ?? true), }); const formattedRoute = Object.assign({}, { path: route.path, name: this.#getNameFromPath(route.segments[route.segments.length - 1]), }, pathMetadata); if (route.children) { formattedRoute.children = this.#formatRoutes(route.children); } return formattedRoute; }) .filter((route) => route.showInNav !== false); return this.#sortRoutes(formatted); } #validateNavOrder(json) { if ('order' in json) { const order = numberConverter(json.order); json.order = order; if (order === undefined || order <= 0 || Number.isNaN(order)) { delete json.order; } } return json; } #getNameFromPath(path) { path = path.replace(/-/g, ' '); return this.#toTitleCase(path); } #toTitleCase(phrase) { return phrase .split(' ') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } #sortRoutes(routes) { const sortedRoutes = routes .filter((route) => !('order' in route)) .sort(sortByName); const routesWithNavOrder = routes .filter((route) => 'order' in route) .sort(sortByName) .sort(sortByOrder); routesWithNavOrder.forEach((route) => { // We know route order is defined in this loop. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const order = route.order; let newIdx = order - 1; const validPosition = () => newIdx < sortedRoutes.length; const positionPreviouslyAssigned = () => { return sortedRoutes[newIdx].order === undefined ? false : sortedRoutes[newIdx].order <= order; }; if (validPosition()) { while (validPosition() && positionPreviouslyAssigned()) { newIdx++; } sortedRoutes.splice(newIdx, 0, route); } else { sortedRoutes.push(route); } }); return sortedRoutes; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouteService, deps: [{ token: i1.Router }, { token: ROUTES, optional: true }, { token: STACHE_ROUTE_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouteService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouteService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.Router }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [ROUTES] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [STACHE_ROUTE_OPTIONS] }] }] }); class StacheRouterModule { static forChild(basePath) { return { ngModule: StacheRouterModule, providers: [ { provide: StacheRouteService, useFactory: (router, routes) => { const options = { basePath, }; return new StacheRouteService(router, routes, options); }, deps: [Router, ROUTES], }, ], }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterModule, providers: [StacheRouteService] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterModule, decorators: [{ type: NgModule, args: [{ providers: [StacheRouteService], }] }] }); /* istanbul ignore file */ /** * NOTICE: DO NOT MODIFY THIS FILE! * The contents of this file were automatically generated by * the 'ng generate @skyux/i18n:lib-resources-module lib/modules/shared/sky-stache' schematic. * To update this file, simply rerun the command. */ const RESOURCES = { 'EN-US': { stache_copyright_label: { message: 'Blackbaud, Inc. All rights reserved.' }, stache_sidebar_label: { message: 'Sidebar Navigation' }, stache_sidebar_toggle_button: { message: 'Toggle sidebar' }, stache_back_to_top_button: { message: 'Scroll back to the top of the page', }, }, }; SkyLibResourcesService.addResources(RESOURCES); /** * Import into any component library module that needs to use resource strings. */ class SkyStacheResourcesModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkyStacheResourcesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.7", ngImport: i0, type: SkyStacheResourcesModule, exports: [SkyI18nModule] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkyStacheResourcesModule, imports: [SkyI18nModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkyStacheResourcesModule, decorators: [{ type: NgModule, args: [{ exports: [SkyI18nModule], }] }] }); function getWindow() { return window; } /** * @internal */ class StacheWindowRef { get nativeWindow() { return getWindow(); } get onResizeStream() { return this.#resizeSubject.asObservable(); } #resizeSubject; constructor() { this.scrollEventStream = fromEvent(this.nativeWindow, 'scroll'); this.#resizeSubject = new ReplaySubject(); this.nativeWindow.addEventListener('resize', (event) => { this.#onResize(event); }); } #onResize(event) { this.#resizeSubject.next(event.target); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheWindowRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheWindowRef }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheWindowRef, decorators: [{ type: Injectable }], ctorParameters: () => [] }); class StacheNavService { #router; #windowRef; constructor(router, windowRef) { this.#router = router; this.#windowRef = windowRef; } navigate(route) { const extras = { queryParamsHandling: 'merge' }; const currentPath = this.#router.url.split('?')[0].split('#')[0]; if (this.isExternal(route)) { this.#windowRef.nativeWindow.location.href = route.path; return; } if (route.fragment) { if (this.#isCurrentRoute(route.path, currentPath)) { this.#navigateInPage(route.fragment); return; } extras.fragment = route.fragment; } if (Array.isArray(route.path)) { void this.#router.navigate(route.path, extras); } else { void this.#router.navigate([route.path], extras); } } isExternal(route) { const routeStr = typeof route === 'string' ? route : typeof route.path === 'string' ? route.path : undefined; return routeStr ? /^(https?|mailto|ftp):+|^(www)/.test(routeStr) : false; } #isCurrentRoute(routePath, currentPath) { let path = routePath; if (Array.isArray(path)) { path = path.join('/'); } return (path === '.' || currentPath.replace(/^\//, '') === path.replace(/^\//, '')); } #navigateInPage(fragment) { const element = this.#windowRef.nativeWindow.document.getElementById(fragment); if (element) { element.scrollIntoView(); this.#windowRef.nativeWindow.location.hash = fragment; } else { // The current page is the path intended, but no element with the fragment exists, scroll to // the top of the page. this.#windowRef.nativeWindow.scroll(0, 0); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavService, deps: [{ token: i1.Router }, { token: StacheWindowRef }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.Router }, { type: StacheWindowRef }] }); class StacheRouterLinkDirective { set stacheRouterLink(value) { if (value === '.') { this.#_stacheRouterLink = this.#routerSvc.getActiveUrl(); } else { this.#_stacheRouterLink = Array.isArray(value) ? value.join('/') : (value ?? ''); } } get stacheRouterLink() { return this.#_stacheRouterLink; } #_stacheRouterLink = ''; #navSvc; #routerSvc; #locationStrategy; constructor(navSvc, routerSvc, elementRef, locationStrategy, renderer) { this.#routerSvc = routerSvc; this.#locationStrategy = locationStrategy; this.#navSvc = navSvc; renderer.setStyle(elementRef.nativeElement, 'cursor', 'pointer'); } ngOnChanges() { this.#updateTargetUrlAndHref(); } ngAfterViewInit() { this.#updateTargetUrlAndHref(); } navigate(event) { if (event.ctrlKey || event.metaKey || event.shiftKey) { return true; } else { event.preventDefault(); this.#navSvc.navigate({ path: this.stacheRouterLink, fragment: this.fragment, }); return true; } } #updateTargetUrlAndHref() { let path = `${this.stacheRouterLink}`; if (this.fragment) { path += `#${this.fragment}`; } if (this.#navSvc.isExternal(path)) { this.href = path; } else { this.href = this.#locationStrategy.prepareExternalUrl(path); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterLinkDirective, deps: [{ token: StacheNavService }, { token: StacheRouteService }, { token: i0.ElementRef }, { token: i3.LocationStrategy }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: StacheRouterLinkDirective, isStandalone: false, selector: "a[stacheRouterLink], [stacheRouterLink]", inputs: { stacheRouterLink: "stacheRouterLink", fragment: "fragment" }, host: { listeners: { "click": "navigate($event)" }, properties: { "href": "this.href" } }, usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheRouterLinkDirective, decorators: [{ type: Directive, args: [{ selector: 'a[stacheRouterLink], [stacheRouterLink]', standalone: false, }] }], ctorParameters: () => [{ type: StacheNavService }, { type: StacheRouteService }, { type: i0.ElementRef }, { type: i3.LocationStrategy }, { type: i0.Renderer2 }], propDecorators: { stacheRouterLink: [{ type: Input }], fragment: [{ type: Input }], href: [{ type: HostBinding }], navigate: [{ type: HostListener, args: ['click', ['$event']] }] } }); class StacheNavComponent { set routes(value) { this.#_routes = value; this.filteredRoutes = this.#filterRestrictedRoutes(value, this.#isAuthenticated); this.#assignActiveStates(); } get routes() { return this.#_routes; } set navType(value) { this.#_navType = value; this.className = value ? `stache-nav-${value}` : undefined; } get navType() { return this.#_navType; } set #isAuthenticated(value) { if (value !== this.#_isAuthenticated) { this.#_isAuthenticated = value; this.filteredRoutes = this.#filterRestrictedRoutes(this.routes, value); } } get #isAuthenticated() { return this.#_isAuthenticated; } #_isAuthenticated = false; #_navType; #_routes; #authSvc; #ngUnsubscribe = new Subject(); #routeSvc; constructor(routeSvc, authSvc) { this.#routeSvc = routeSvc; this.#authSvc = authSvc; } ngOnInit() { this.#assignActiveStates(); this.#authSvc.isAuthenticated .pipe(takeUntil(this.#ngUnsubscribe)) .subscribe((isAuthenticated) => { this.#isAuthenticated = isAuthenticated; }); } ngOnDestroy() { this.#ngUnsubscribe.next(); this.#ngUnsubscribe.complete(); } #assignActiveStates() { const activeUrl = this.#routeSvc.getActiveUrl(); if (this.filteredRoutes) { this.filteredRoutes.forEach((route) => { route.isActive = this.#isActive(activeUrl, route); route.isCurrent = this.#isCurrent(activeUrl, route); }); } } #isActive(activeUrl, route) { let path = route.path; let navDepth; if (Array.isArray(path)) { navDepth = path.length; path = path.join('/'); } else { navDepth = path.split('/').length; } if (path.indexOf('/') !== 0) { path = `/${path}`; } const isActiveParent = navDepth > 1 && `${activeUrl}/`.indexOf(`${path}/`) === 0; return isActiveParent || activeUrl === path; } #isCurrent(activeUrl, route) { let path = route.path; if (Array.isArray(path)) { path = path.join('/'); } return activeUrl === `/${path}`; } #filterRestrictedRoutes(routes, isAuthenticated) { if (!routes || routes.length === 0 || isAuthenticated) { return routes; } return routes.filter((route) => { return !route.restricted; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavComponent, deps: [{ token: StacheRouteService }, { token: StacheAuthService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: StacheNavComponent, isStandalone: false, selector: "stache-nav", inputs: { routes: "routes", navType: "navType" }, ngImport: i0, template: "@if (filteredRoutes?.length) {\n <nav class=\"stache-nav\" [ngClass]=\"className\">\n <ol class=\"stache-nav-list\">\n @for (route of filteredRoutes; track $index) {\n <li\n class=\"stache-nav-list-item\"\n [attr.aria-current]=\"route.isCurrent ? 'page' : false\"\n [ngClass]=\"{ active: route.isActive, current: route.isCurrent }\"\n >\n <a\n class=\"stache-nav-anchor\"\n queryParamsHandling=\"merge\"\n [stacheRouterLink]=\"route.path\"\n [fragment]=\"route.fragment\"\n >{{ route.name }}</a\n >\n @if (route.children) {\n <stache-nav\n navType=\"sidebar stache-nav-sub\"\n [routes]=\"route.children\"\n />\n }\n </li>\n }\n </ol>\n </nav>\n}\n", styles: [".stache-nav-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}.stache-nav-anchor:hover{cursor:pointer}\n"], dependencies: [{ kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: StacheNavComponent, selector: "stache-nav", inputs: ["routes", "navType"] }, { kind: "directive", type: StacheRouterLinkDirective, selector: "a[stacheRouterLink], [stacheRouterLink]", inputs: ["stacheRouterLink", "fragment"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavComponent, decorators: [{ type: Component, args: [{ selector: 'stache-nav', standalone: false, template: "@if (filteredRoutes?.length) {\n <nav class=\"stache-nav\" [ngClass]=\"className\">\n <ol class=\"stache-nav-list\">\n @for (route of filteredRoutes; track $index) {\n <li\n class=\"stache-nav-list-item\"\n [attr.aria-current]=\"route.isCurrent ? 'page' : false\"\n [ngClass]=\"{ active: route.isActive, current: route.isCurrent }\"\n >\n <a\n class=\"stache-nav-anchor\"\n queryParamsHandling=\"merge\"\n [stacheRouterLink]=\"route.path\"\n [fragment]=\"route.fragment\"\n >{{ route.name }}</a\n >\n @if (route.children) {\n <stache-nav\n navType=\"sidebar stache-nav-sub\"\n [routes]=\"route.children\"\n />\n }\n </li>\n }\n </ol>\n </nav>\n}\n", styles: [".stache-nav-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}.stache-nav-anchor:hover{cursor:pointer}\n"] }] }], ctorParameters: () => [{ type: StacheRouteService }, { type: StacheAuthService }], propDecorators: { routes: [{ type: Input }], navType: [{ type: Input }] } }); class StacheNavModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.7", ngImport: i0, type: StacheNavModule, declarations: [StacheNavComponent, StacheRouterLinkDirective], imports: [CommonModule, RouterModule, SkyStacheResourcesModule, StacheRouterModule], exports: [StacheNavComponent, StacheRouterLinkDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavModule, providers: [StacheAuthService, StacheNavService, StacheWindowRef], imports: [CommonModule, RouterModule, SkyStacheResourcesModule, StacheRouterModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheNavModule, decorators: [{ type: NgModule, args: [{ declarations: [StacheNavComponent, StacheRouterLinkDirective], imports: [ CommonModule, RouterModule, SkyStacheResourcesModule, StacheRouterModule, ], exports: [StacheNavComponent, StacheRouterLinkDirective], providers: [StacheAuthService, StacheNavService, StacheWindowRef], }] }] }); const SEARCH_KEYS = ['name', 'summary']; /** * @deprecated Use SKY UX action buttons instead: https://developer.blackbaud.com/skyux/components/action-button */ class StacheActionButtonsComponent { constructor() { this.filteredRoutes = []; this.searchText = ''; this.#_showSearch = true; } set routes(value) { this.#_routes = value; this.filteredRoutes = value || []; } get routes() { return this.#_routes; } set showSearch(value) { this.#_showSearch = booleanConverter(value) !== false; } get showSearch() { return this.#_showSearch; } #_routes; #_showSearch; onKeyUp(event) { const searchText = event.target.value; this.searchApplied(searchText); } searchApplied(searchText) { this.searchText = searchText; if (!searchText) { return; } if (this.routes) { const query = searchText.toLowerCase(); this.filteredRoutes = this.routes.filter((route) => { const matchingFields = SEARCH_KEYS.filter((key) => { const value = route[key]; return value && value.toLowerCase().includes(query); }); return matchingFields.length > 0; }); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: StacheActionButtonsComponent, isStandalone: false, selector: "stache-action-buttons", inputs: { routes: "routes", showSearch: "showSearch" }, ngImport: i0, template: "@if (routes?.length) {\n <div class=\"stache-action-buttons\">\n @if (showSearch) {\n <div class=\"stache-action-buttons-search-container\">\n <sky-search\n expandMode=\"fit\"\n [searchText]=\"searchText\"\n (searchApply)=\"searchApplied($event)\"\n (keyup)=\"onKeyUp($event)\"\n />\n </div>\n }\n <sky-row>\n <nav class=\"stache-action-buttons-nav-container\">\n @for (route of filteredRoutes; track $index) {\n <sky-column [screenSmall]=\"6\" [screenMedium]=\"4\" [screenLarge]=\"3\">\n <a\n class=\"stache-action-button-override sky-action-button sky-btn-default sky-rounded-corners\"\n [stacheRouterLink]=\"route.path\"\n [fragment]=\"route.fragment\"\n >\n <div>\n @if (route.icon) {\n <sky-action-button-icon [iconName]=\"route.icon\" />\n }\n <sky-action-button-header>\n {{ route.name }}\n </sky-action-button-header>\n </div>\n <sky-action-button-details>\n {{ route.summary }}\n </sky-action-button-details>\n </a>\n </sky-column>\n }\n </nav>\n </sky-row>\n </div>\n}\n", styles: [".stache-action-buttons{margin:10px 0}.stache-action-button-override{display:flex;flex-direction:column;padding:30px 20px;text-align:center;border:var(--sky-border-width-container-base, 1px) solid var(--sky-color-border-container-base, var(--sky-border-color-neutral-medium));margin:15px 0;width:100%}.stache-action-button-override ::ng-deep .sky-action-button-header{display:block;margin-bottom:20px}.stache-action-button-override:hover{text-decoration:none}.stache-action-buttons-search-container{position:relative;display:flex;min-height:49px;margin-bottom:10px}.stache-action-buttons-nav-container{width:100%;display:flex;flex-wrap:wrap}::ng-deep .stache-action-buttons .sky-column{display:inline-flex}::ng-deep .stache-action-buttons .sky-search-dismiss-absolute{padding:5px 0!important}\n"], dependencies: [{ kind: "component", type: i1$1.λ3, selector: "sky-action-button-details" }, { kind: "component", type: i1$1.λ4, selector: "sky-action-button-header" }, { kind: "component", type: i1$1.λ5, selector: "sky-action-button-icon", inputs: ["iconName"] }, { kind: "component", type: i1$1.λ23, selector: "sky-row", inputs: ["reverseColumnOrder"] }, { kind: "component", type: i1$1.λ24, selector: "sky-column", inputs: ["screenXSmall", "screenSmall", "screenMedium", "screenLarge"] }, { kind: "component", type: i2.SkySearchComponent, selector: "sky-search", inputs: ["ariaLabel", "ariaLabelledBy", "searchText", "expandMode", "debounceTime", "disabled", "placeholderText"], outputs: ["searchApply", "searchChange", "searchClear"] }, { kind: "directive", type: StacheRouterLinkDirective, selector: "a[stacheRouterLink], [stacheRouterLink]", inputs: ["stacheRouterLink", "fragment"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsComponent, decorators: [{ type: Component, args: [{ selector: 'stache-action-buttons', standalone: false, template: "@if (routes?.length) {\n <div class=\"stache-action-buttons\">\n @if (showSearch) {\n <div class=\"stache-action-buttons-search-container\">\n <sky-search\n expandMode=\"fit\"\n [searchText]=\"searchText\"\n (searchApply)=\"searchApplied($event)\"\n (keyup)=\"onKeyUp($event)\"\n />\n </div>\n }\n <sky-row>\n <nav class=\"stache-action-buttons-nav-container\">\n @for (route of filteredRoutes; track $index) {\n <sky-column [screenSmall]=\"6\" [screenMedium]=\"4\" [screenLarge]=\"3\">\n <a\n class=\"stache-action-button-override sky-action-button sky-btn-default sky-rounded-corners\"\n [stacheRouterLink]=\"route.path\"\n [fragment]=\"route.fragment\"\n >\n <div>\n @if (route.icon) {\n <sky-action-button-icon [iconName]=\"route.icon\" />\n }\n <sky-action-button-header>\n {{ route.name }}\n </sky-action-button-header>\n </div>\n <sky-action-button-details>\n {{ route.summary }}\n </sky-action-button-details>\n </a>\n </sky-column>\n }\n </nav>\n </sky-row>\n </div>\n}\n", styles: [".stache-action-buttons{margin:10px 0}.stache-action-button-override{display:flex;flex-direction:column;padding:30px 20px;text-align:center;border:var(--sky-border-width-container-base, 1px) solid var(--sky-color-border-container-base, var(--sky-border-color-neutral-medium));margin:15px 0;width:100%}.stache-action-button-override ::ng-deep .sky-action-button-header{display:block;margin-bottom:20px}.stache-action-button-override:hover{text-decoration:none}.stache-action-buttons-search-container{position:relative;display:flex;min-height:49px;margin-bottom:10px}.stache-action-buttons-nav-container{width:100%;display:flex;flex-wrap:wrap}::ng-deep .stache-action-buttons .sky-column{display:inline-flex}::ng-deep .stache-action-buttons .sky-search-dismiss-absolute{padding:5px 0!important}\n"] }] }], propDecorators: { routes: [{ type: Input }], showSearch: [{ type: Input }] } }); class StacheActionButtonsModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsModule, declarations: [StacheActionButtonsComponent], imports: [CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule], exports: [StacheActionButtonsComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsModule, imports: [CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheActionButtonsModule, decorators: [{ type: NgModule, args: [{ declarations: [StacheActionButtonsComponent], imports: [ CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule, ], exports: [StacheActionButtonsComponent], }] }] }); const HAS_VIEWPORT_ADJUSTMENT_CLASS_NAME = 'stache-viewport-adjusted'; class StacheViewportAdapterService { #windowRef = inject(StacheWindowRef); #renderer = inject(RendererFactory2).createRenderer(undefined, null); checkForViewportAdjustment() { if (this.viewportAdjusted()) { this.#applyClassToBody(); } } getHeight() { if (this.viewportAdjusted()) { return parseInt(getComputedStyle(this.#windowRef.nativeWindow.document.documentElement).getPropertyValue('--sky-viewport-top'), 10); } return 0; } viewportAdjusted() { // Converts the element's existence to a boolean. return (parseInt(getComputedStyle(this.#windowRef.nativeWindow.document.documentElement).getPropertyValue('--sky-viewport-top'), 10) > 0); } #applyClassToBody() { this.#renderer.addClass(this.#windowRef.nativeWindow.document.body, HAS_VIEWPORT_ADJUSTMENT_CLASS_NAME); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheViewportAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheViewportAdapterService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheViewportAdapterService, decorators: [{ type: Injectable }] }); const AFFIX_CLASS_NAME = 'stache-affix-top'; class StacheAffixTopDirective { #footerWrapper; #viewportAdjustmentHeight; #offsetTop; #element; #renderer; #elementRef; #viewportSvc; #windowRef; constructor(renderer, elementRef, viewportService, windowRef) { this.isAffixed = false; this.#viewportAdjustmentHeight = 0; this.#offsetTop = 0; this.#renderer = renderer; this.#elementRef = elementRef; this.#viewportSvc = viewportService; this.#windowRef = windowRef; } ngAfterViewInit() { const nativeElement = this.#elementRef.nativeElement; if (this.#isComponent(nativeElement) && nativeElement.children[0]) { this.#element = nativeElement.children[0]; } else { this.#element = nativeElement; } } // This ensures that an element that should be affixed to the top on initial load is affixed. // In the AfterContentInit lifecycle hook to allow content to load if that affects positioning. ngAfterContentInit() { this.onWindowScroll(); } onWindowScroll() { this.#viewportAdjustmentHeight = this.#viewportSvc.getHeight(); this.#footerWrapper = this.#windowRef.nativeWindow.document.querySelector('.stache-footer-wrapper'); this.#setMaxHeight(); if (this.#element) { if (!this.isAffixed) { this.#offsetTop = this.#getOffset(this.#element); } else { this.#offsetTop = this.#element.offsetTop; } } const windowIsScrolledBeyondElement = this.#offsetTop - this.#viewportAdjustmentHeight < this.#windowRef.nativeWindow.pageYOffset; if (windowIsScrolledBeyondElement) { this.#affixToTop(); } else { this.#resetElement(); } } #isComponent(element) { let isComponent = false; Array.prototype.slice.call(element.attributes).forEach((item) => { if (!isComponent && item.name.indexOf('_nghost') === 0) { isComponent = true; } }); return isComponent; } #getOffset(element) { let offset = element.offsetTop; let el = element; while (el.offsetParent) { const parent = el.offsetParent; offset += parent.offsetTop; el = parent; } return offset; } #affixToTop() { if (!this.isAffixed && this.#element) { this.isAffixed = true; this.#renderer.setStyle(this.#element, 'position', 'fixed'); this.#renderer.setStyle(this.#element, 'top', 'var(--sky-viewport-top)'); this.#renderer.setStyle(this.#element, 'width', 'inherit'); this.#renderer.addClass(this.#element, AFFIX_CLASS_NAME); } } #resetElement() { if (this.isAffixed) { this.isAffixed = false; this.#renderer.setStyle(this.#element, 'position', 'static'); this.#renderer.removeStyle(this.#element, 'top'); this.#renderer.removeClass(this.#element, AFFIX_CLASS_NAME); } } #setMaxHeight() { let maxHeight = `calc(100% - var(--sky-viewport-top))`; if (this.#footerWrapper && this.#footerIsVisible()) { maxHeight = `calc(${this.#getOffset(this.#footerWrapper) - this.#windowRef.nativeWindow.pageYOffset}px - var(--sky-viewport-top))`; } /* istanbul ignore else */ if (this.#element) { this.#renderer.setStyle(this.#element, 'height', `${maxHeight}`); } } #footerIsVisible() { /*istanbul ignore else*/ if (this.#footerWrapper) { return (this.#footerWrapper.getBoundingClientRect().top <= this.#windowRef.nativeWindow.innerHeight); } else { return false; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixTopDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: StacheViewportAdapterService }, { token: StacheWindowRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: StacheAffixTopDirective, isStandalone: false, selector: "[stacheAffixTop]", host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixTopDirective, decorators: [{ type: Directive, args: [{ selector: '[stacheAffixTop]', standalone: false, }] }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: StacheViewportAdapterService }, { type: StacheWindowRef }], propDecorators: { onWindowScroll: [{ type: HostListener, args: ['window:scroll'] }] } }); class StacheAffixComponent { #windowRef; #windowSubscription; #changeDetector; constructor(windowRef, changeDetector) { this.#windowRef = windowRef; this.#changeDetector = changeDetector; this.#windowSubscription = this.#windowRef.onResizeStream.subscribe(() => { this.#setElementRefDimensions(); }); } ngAfterViewInit() { this.#setElementRefDimensions(); this.#changeDetector.detectChanges(); } ngOnDestroy() { this.#windowSubscription.unsubscribe(); } getStyles() { return { 'min-height': this.#getCssMinHeight(), 'max-width': this.#getCssMaxWidth(), position: this.#getCssPosition(), }; } #setElementRefDimensions() { /* istanbul ignore else */ if (this.wrapper) { this.minHeightFormatted = `${this.wrapper.nativeElement.offsetHeight}px`; this.maxWidthFormatted = `${this.wrapper.nativeElement.offsetWidth}px`; } } #getCssPosition() { if (this.affixTopDirective?.isAffixed) { return 'relative'; } return 'static'; } #getCssMinHeight() { if (this.affixTopDirective?.isAffixed) { return this.minHeightFormatted; } return 'auto'; } #getCssMaxWidth() { if (this.affixTopDirective?.isAffixed) { return this.maxWidthFormatted; } return '100%'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixComponent, deps: [{ token: StacheWindowRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: StacheAffixComponent, isStandalone: false, selector: "stache-affix", viewQueries: [{ propertyName: "wrapper", first: true, predicate: ["stacheAffixWrapper"], descendants: true, read: ElementRef }, { propertyName: "affixTopDirective", first: true, predicate: StacheAffixTopDirective, descendants: true, read: StacheAffixTopDirective, static: true }], ngImport: i0, template: "<div #stacheAffixWrapper>\n <div class=\"stache-affix\" [ngStyle]=\"getStyles()\">\n <div stacheAffixTop>\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [".stache-affix:after{content:\"\";display:flex}::ng-deep .stache-affix .stache-affix-top{max-width:inherit}\n"], dependencies: [{ kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: StacheAffixTopDirective, selector: "[stacheAffixTop]" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixComponent, decorators: [{ type: Component, args: [{ selector: 'stache-affix', standalone: false, template: "<div #stacheAffixWrapper>\n <div class=\"stache-affix\" [ngStyle]=\"getStyles()\">\n <div stacheAffixTop>\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [".stache-affix:after{content:\"\";display:flex}::ng-deep .stache-affix .stache-affix-top{max-width:inherit}\n"] }] }], ctorParameters: () => [{ type: StacheWindowRef }, { type: i0.ChangeDetectorRef }], propDecorators: { wrapper: [{ type: ViewChild, args: ['stacheAffixWrapper', { read: ElementRef, static: false, }] }], affixTopDirective: [{ type: ViewChild, args: [StacheAffixTopDirective, { read: StacheAffixTopDirective, static: true, }] }] } }); class StacheAffixModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixModule, declarations: [StacheAffixComponent, StacheAffixTopDirective], imports: [CommonModule], exports: [StacheAffixComponent, StacheAffixTopDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixModule, providers: [StacheViewportAdapterService, StacheWindowRef], imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StacheAffixModule, decorators: [{ type: NgModule, args: [{ declarations: [StacheAffixComponent, StacheAffixTopDirective], imports: [CommonModule], exports: [StacheAffixComponent, StacheAffixTopDirective], providers: [StacheViewportAdapterService, StacheWindowRef], }] }] }); class StacheGoogleAnalyticsDirective { #nonce; constructor(windowRef, configService, router) { this.windowRef = windowRef; this.configService = configService; this.router = router; this.#nonce = inject(CSP_NONCE, { optional: true }); this.tagManagerContainerId = 'GTM-W56QP9';