UNPKG

@blackbaud/skyux-lib-stache

Version:

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

1,029 lines (1,009 loc) 177 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, ElementRef, ViewChild, inject, CSP_NONCE, ChangeDetectionStrategy, TemplateRef, ViewEncapsulation } from '@angular/core'; import * as i2 from '@skyux/layout'; import { SkyActionButtonModule, SkyFluidGridModule } from '@skyux/layout'; import * as i3$1 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$1 from '@skyux/config'; import lodashGet from 'lodash.get'; import * as i2$2 from '@skyux/core'; import { SkyMediaBreakpoints } from '@skyux/core'; import * as i1$2 from '@angular/platform-browser'; class StacheAuthService { constructor() { this.isAuthenticated = of(false); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheAuthService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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: "19.2.4", 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: "19.2.4", ngImport: i0, type: StacheRouteService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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: "19.2.4", ngImport: i0, type: StacheRouterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: StacheRouterModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheRouterModule, providers: [StacheRouteService] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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: "19.2.4", ngImport: i0, type: SkyStacheResourcesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: SkyStacheResourcesModule, exports: [SkyI18nModule] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SkyStacheResourcesModule, imports: [SkyI18nModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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: "19.2.4", ngImport: i0, type: StacheWindowRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheWindowRef }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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)) { this.#router.navigate(route.path, extras); } else { 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: "19.2.4", ngImport: i0, type: StacheNavService, deps: [{ token: i1.Router }, { token: StacheWindowRef }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheNavService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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: "19.2.4", 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: "19.2.4", type: StacheRouterLinkDirective, isStandalone: false, selector: "[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: "19.2.4", ngImport: i0, type: StacheRouterLinkDirective, decorators: [{ type: Directive, args: [{ selector: '[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: "19.2.4", ngImport: i0, type: StacheNavComponent, deps: [{ token: StacheRouteService }, { token: StacheAuthService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: StacheNavComponent, isStandalone: false, selector: "stache-nav", inputs: { routes: "routes", navType: "navType" }, ngImport: i0, template: "<nav *ngIf=\"filteredRoutes?.length\" class=\"stache-nav\" [ngClass]=\"className\">\n <ol class=\"stache-nav-list\">\n <li\n class=\"stache-nav-list-item\"\n *ngFor=\"let route of filteredRoutes\"\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 stacheRouterLink=\"{{ route.path }}\"\n fragment=\"{{ route.fragment }}\"\n queryParamsHandling=\"merge\"\n >{{ route.name }}</a\n >\n <stache-nav\n *ngIf=\"route.children\"\n navType=\"sidebar stache-nav-sub\"\n [routes]=\"route.children\"\n >\n </stache-nav>\n </li>\n </ol>\n</nav>\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: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: StacheNavComponent, selector: "stache-nav", inputs: ["routes", "navType"] }, { kind: "directive", type: StacheRouterLinkDirective, selector: "[stacheRouterLink]", inputs: ["stacheRouterLink", "fragment"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheNavComponent, decorators: [{ type: Component, args: [{ selector: 'stache-nav', standalone: false, template: "<nav *ngIf=\"filteredRoutes?.length\" class=\"stache-nav\" [ngClass]=\"className\">\n <ol class=\"stache-nav-list\">\n <li\n class=\"stache-nav-list-item\"\n *ngFor=\"let route of filteredRoutes\"\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 stacheRouterLink=\"{{ route.path }}\"\n fragment=\"{{ route.fragment }}\"\n queryParamsHandling=\"merge\"\n >{{ route.name }}</a\n >\n <stache-nav\n *ngIf=\"route.children\"\n navType=\"sidebar stache-nav-sub\"\n [routes]=\"route.children\"\n >\n </stache-nav>\n </li>\n </ol>\n</nav>\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: "19.2.4", ngImport: i0, type: StacheNavModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", 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: "19.2.4", ngImport: i0, type: StacheNavModule, providers: [StacheAuthService, StacheNavService, StacheWindowRef], imports: [CommonModule, RouterModule, SkyStacheResourcesModule, StacheRouterModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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']; 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: "19.2.4", ngImport: i0, type: StacheActionButtonsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: StacheActionButtonsComponent, isStandalone: false, selector: "stache-action-buttons", inputs: { routes: "routes", showSearch: "showSearch" }, ngImport: i0, template: "<div class=\"stache-action-buttons\" *ngIf=\"routes?.length\">\n <div *ngIf=\"showSearch\" 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 </sky-search>\n </div>\n <sky-row>\n <nav class=\"stache-action-buttons-nav-container\">\n <sky-column\n [screenSmall]=\"6\"\n [screenMedium]=\"4\"\n [screenLarge]=\"3\"\n *ngFor=\"let route of filteredRoutes\"\n >\n <a\n stacheRouterLink=\"{{ route.path }}\"\n fragment=\"{{ route.fragment }}\"\n class=\"stache-action-button-override sky-action-button sky-btn-default sky-rounded-corners\"\n >\n <div>\n <sky-action-button-icon\n *ngIf=\"route.icon\"\n iconType=\"{{ route.icon }}\"\n >\n </sky-action-button-icon>\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 </nav>\n </sky-row>\n</div>\n", styles: [".stache-action-buttons{margin:10px 0}.stache-action-button-override{display:flex;flex-direction:column;padding:30px 20px;text-align:center;border:1px solid #cdcfd2;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: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.λ3, selector: "sky-action-button-details" }, { kind: "component", type: i2.λ4, selector: "sky-action-button-header" }, { kind: "component", type: i2.λ5, selector: "sky-action-button-icon", inputs: ["iconType", "iconName"] }, { kind: "component", type: i2.λ23, selector: "sky-row", inputs: ["reverseColumnOrder"] }, { kind: "component", type: i2.λ24, selector: "sky-column", inputs: ["screenXSmall", "screenSmall", "screenMedium", "screenLarge"] }, { kind: "component", type: i3$1.SkySearchComponent, selector: "sky-search", inputs: ["ariaLabel", "ariaLabelledBy", "searchText", "expandMode", "debounceTime", "disabled", "placeholderText"], outputs: ["searchApply", "searchChange", "searchClear"] }, { kind: "directive", type: StacheRouterLinkDirective, selector: "[stacheRouterLink]", inputs: ["stacheRouterLink", "fragment"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheActionButtonsComponent, decorators: [{ type: Component, args: [{ selector: 'stache-action-buttons', standalone: false, template: "<div class=\"stache-action-buttons\" *ngIf=\"routes?.length\">\n <div *ngIf=\"showSearch\" 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 </sky-search>\n </div>\n <sky-row>\n <nav class=\"stache-action-buttons-nav-container\">\n <sky-column\n [screenSmall]=\"6\"\n [screenMedium]=\"4\"\n [screenLarge]=\"3\"\n *ngFor=\"let route of filteredRoutes\"\n >\n <a\n stacheRouterLink=\"{{ route.path }}\"\n fragment=\"{{ route.fragment }}\"\n class=\"stache-action-button-override sky-action-button sky-btn-default sky-rounded-corners\"\n >\n <div>\n <sky-action-button-icon\n *ngIf=\"route.icon\"\n iconType=\"{{ route.icon }}\"\n >\n </sky-action-button-icon>\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 </nav>\n </sky-row>\n</div>\n", styles: [".stache-action-buttons{margin:10px 0}.stache-action-button-override{display:flex;flex-direction:column;padding:30px 20px;text-align:center;border:1px solid #cdcfd2;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: "19.2.4", ngImport: i0, type: StacheActionButtonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: StacheActionButtonsModule, declarations: [StacheActionButtonsComponent], imports: [CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule], exports: [StacheActionButtonsComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheActionButtonsModule, imports: [CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheActionButtonsModule, decorators: [{ type: NgModule, args: [{ declarations: [StacheActionButtonsComponent], imports: [ CommonModule, SkyActionButtonModule, SkyFluidGridModule, SkySearchModule, StacheNavModule, ], exports: [StacheActionButtonsComponent], }] }] }); const HAS_OMNIBAR_CLASS_NAME = 'stache-omnibar-enabled'; const EXPECTED_OMNIBAR_HEIGHT = 50; class StacheOmnibarAdapterService { #element; #renderer; #windowRef; constructor(windowRef, rendererFactory) { this.#windowRef = windowRef; this.#renderer = rendererFactory.createRenderer(undefined, null); this.#element = windowRef.nativeWindow.document.querySelector('.sky-omnibar-iframe'); } checkForOmnibar() { if (this.omnibarEnabled()) { this.#applyClassToBody(); } } getHeight() { if (this.omnibarEnabled()) { return EXPECTED_OMNIBAR_HEIGHT; } return 0; } omnibarEnabled() { // Converts the element's existence to a boolean. return !!this.#element; } #applyClassToBody() { this.#renderer.addClass(this.#windowRef.nativeWindow.document.body, HAS_OMNIBAR_CLASS_NAME); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheOmnibarAdapterService, deps: [{ token: StacheWindowRef }, { token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheOmnibarAdapterService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheOmnibarAdapterService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: StacheWindowRef }, { type: i0.RendererFactory2 }] }); const AFFIX_CLASS_NAME = 'stache-affix-top'; class StacheAffixTopDirective { #footerWrapper; #omnibarHeight; #offsetTop; #element; #renderer; #elementRef; #omnibarSvc; #windowRef; constructor(renderer, elementRef, omnibarSvc, windowRef) { this.isAffixed = false; this.#omnibarHeight = 0; this.#offsetTop = 0; this.#renderer = renderer; this.#elementRef = elementRef; this.#omnibarSvc = omnibarSvc; this.#windowRef = windowRef; } ngAfterViewInit() { this.#footerWrapper = this.#windowRef.nativeWindow.document.querySelector('.stache-footer-wrapper'); const nativeElement = this.#elementRef.nativeElement; if (this.#isComponent(nativeElement) && nativeElement.children[0]) { this.#element = nativeElement.children[0]; } else { this.#element = nativeElement; } } onWindowScroll() { this.#omnibarHeight = this.#omnibarSvc.getHeight(); this.#setMaxHeight(); if (this.#element && !this.isAffixed) { this.#offsetTop = this.#getOffset(this.#element); } const windowIsScrolledBeyondElement = this.#offsetTop - this.#omnibarHeight <= 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', '0px'); 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.removeClass(this.#element, AFFIX_CLASS_NAME); } } #setMaxHeight() { let maxHeight = `calc(100% - ${this.#omnibarHeight}px)`; if (this.#footerWrapper && this.#footerIsVisible()) { maxHeight = `${this.#getOffset(this.#footerWrapper) - this.#windowRef.nativeWindow.pageYOffset - this.#omnibarHeight}px`; } /* 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: "19.2.4", ngImport: i0, type: StacheAffixTopDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: StacheOmnibarAdapterService }, { token: StacheWindowRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.4", type: StacheAffixTopDirective, isStandalone: false, selector: "[stacheAffixTop]", host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheAffixTopDirective, decorators: [{ type: Directive, args: [{ selector: '[stacheAffixTop]', standalone: false, }] }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: StacheOmnibarAdapterService }, { 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: "19.2.4", ngImport: i0, type: StacheAffixComponent, deps: [{ token: StacheWindowRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", 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></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}@media (min-width: 768px){::ng-deep .stache-omnibar-enabled .stache-affix-top{margin-top:50px}}\n"], dependencies: [{ kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: StacheAffixTopDirective, selector: "[stacheAffixTop]" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", 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></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}@media (min-width: 768px){::ng-deep .stache-omnibar-enabled .stache-affix-top{margin-top:50px}}\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: "19.2.4", ngImport: i0, type: StacheAffixModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: StacheAffixModule, declarations: [StacheAffixComponent, StacheAffixTopDirective], imports: [CommonModule], exports: [StacheAffixComponent, StacheAffixTopDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheAffixModule, providers: [StacheOmnibarAdapterService, StacheWindowRef], imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StacheAffixModule, decorators: [{ type: NgModule, args: [{ declarations: [StacheAffixComponent, StacheAffixTopDirective], imports: [CommonModule], exports: [StacheAffixComponent, StacheAffixTopDirective], providers: [StacheOmnibarAdapterService, 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'; this.analyticsClientId = 'UA-2418840-1'; this.isEnabled = true; } ngOnInit() { const isLoaded = this.windowRef.nativeWindow.ga; const isProduction = this.configService.runtime.command === 'build'; this.updateDefaultConfigs(); if (this.isEnabled && isProduction && !isLoaded) { this.addGoogleTagManagerScript(); this.initGoogleAnalytics(); this.bindPageViewsToRouter(); } } addGoogleTagManagerScript() { const win = this.windowRef.nativeWindow; win.dataLayer ??= [];