@blackbaud/skyux-lib-stache
Version:
This library was generated with [Nx](https://nx.dev).
1,022 lines (1,002 loc) • 186 kB
JavaScript
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';