UNPKG

ng-sidebar-pro

Version:

A complete and professional sidebar pro library for modern Angular applications

1,236 lines (1,205 loc) 84.1 kB
import * as i0 from '@angular/core'; import { inject, signal, computed, Injectable, DOCUMENT, input, Component, output, ViewEncapsulation, Directive, HostListener } from '@angular/core'; import * as i1 from '@angular/router'; import { Router, NavigationEnd, RouterModule } from '@angular/router'; import { filter, map } from 'rxjs/operators'; import { BehaviorSubject } from 'rxjs'; import { CommonModule } from '@angular/common'; import * as i2 from '@angular/material/list'; import { MatListModule } from '@angular/material/list'; import * as i3 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i2$1 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i4$1 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import * as i4 from '@angular/material/tooltip'; import { MatTooltipModule, MatTooltip } from '@angular/material/tooltip'; import * as i5 from '@angular/material/badge'; import { MatBadgeModule } from '@angular/material/badge'; import { trigger, transition, style, animate } from '@angular/animations'; import * as i3$1 from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu'; import { MatDividerModule } from '@angular/material/divider'; class NavigationService { router = inject(Router); configSubject = new BehaviorSubject(null); collapsedSubject = new BehaviorSubject(false); searchTermSubject = new BehaviorSubject(''); // Signals config = signal(null); collapsed = signal(false); searchTerm = signal(''); // Computed values filteredItems = computed(() => { const config = this.config(); const search = this.searchTerm(); if (!config || !search) { return config?.items || []; } return this.filterItemsBySearch(config.items, search); }); currentRoute = signal(''); breadcrumbs = computed(() => { const config = this.config(); const route = this.currentRoute(); if (!config || !route) return []; return this.generateBreadcrumbs(config.items, route); }); constructor() { // Listen to route changes this.router.events .pipe(filter(event => event instanceof NavigationEnd), map(event => event.url)) .subscribe(url => { this.currentRoute.set(url); }); } // Configuration methods setConfig(config) { this.config.set(config); this.configSubject.next(config); } getConfig() { return this.configSubject.asObservable(); } // Collapse methods setCollapsed(collapsed) { this.collapsed.set(collapsed); this.collapsedSubject.next(collapsed); } toggleCollapsed() { const current = this.collapsed(); this.setCollapsed(!current); } getCollapsed() { return this.collapsedSubject.asObservable(); } // Search methods setSearchTerm(term) { this.searchTerm.set(term); this.searchTermSubject.next(term); } getSearchTerm() { return this.searchTermSubject.asObservable(); } // Navigation methods navigateToItem(item) { if (item.external) { window.open(item.route, item.target || '_blank'); return; } if (item.redirectTo) { this.router.navigate([item.redirectTo]); return; } this.router.navigate([item.route]); } // Utility methods findItemById(id) { const config = this.config(); if (!config) return null; return this.findItemInList(config.items, id); } findItemByRoute(route) { const config = this.config(); if (!config) return null; return this.findItemByRouteInList(config.items, route); } isItemActive(item) { const currentRoute = this.currentRoute(); return currentRoute.startsWith(item.route); } canAccessItem(item) { // Implement your access control logic here // This is a simplified version if (item.disabled || item.hidden) return false; // Check permissions, roles, etc. // You can inject your auth service here return true; } filterItemsBySearch(items, searchTerm) { const filtered = []; for (const item of items) { if (this.itemMatchesSearch(item, searchTerm)) { filtered.push(item); } else if (item.subItems) { const filteredSubItems = this.filterItemsBySearch(item.subItems, searchTerm); if (filteredSubItems.length > 0) { filtered.push({ ...item, subItems: filteredSubItems }); } } } return filtered; } itemMatchesSearch(item, searchTerm) { const term = searchTerm.toLowerCase(); return item.label.toLowerCase().includes(term) || (item.tooltip && item.tooltip.toLowerCase().includes(term)) || (item.data?.description && item.data.description.toLowerCase().includes(term)) || false; } findItemInList(items, id) { for (const item of items) { if (item.id === id) return item; if (item.subItems) { const found = this.findItemInList(item.subItems, id); if (found) return found; } } return null; } findItemByRouteInList(items, route) { for (const item of items) { if (item.route === route) return item; if (item.subItems) { const found = this.findItemByRouteInList(item.subItems, route); if (found) return found; } } return null; } generateBreadcrumbs(items, route) { const breadcrumbs = []; const findPath = (itemList, targetRoute, path = []) => { for (const item of itemList) { const currentPath = [...path, item]; if (item.route === targetRoute) { breadcrumbs.push(...currentPath); return true; } if (item.subItems && findPath(item.subItems, targetRoute, currentPath)) { return true; } } return false; }; findPath(items, route); return breadcrumbs; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class NavigationThemeService { document = inject(DOCUMENT); currentTheme = signal('modern-light'); customStyles = signal(null); // Built-in themes themes = { 'modern-light': { colors: { primary: '#2563eb', accent: '#3b82f6', background: '#ffffff', surface: '#f8fafc', text: '#1e293b', textSecondary: '#64748b', border: '#e2e8f0', hover: '#f1f5f9', active: '#e0e7ff', disabled: '#94a3b8' }, typography: { fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif', fontSize: '14px', fontWeight: '500', lineHeight: '1.5' }, spacing: { padding: '12px 16px', margin: '4px 0', itemSpacing: '2px', levelIndent: '20px' }, borders: { radius: '8px', width: '1px', style: 'solid' }, shadows: { small: '0 1px 2px 0 rgb(0 0 0 / 0.05)', medium: '0 4px 6px -1px rgb(0 0 0 / 0.1)', large: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }, animations: { duration: '200ms', easing: 'cubic-bezier(0.4, 0, 0.2, 1)', expandCollapse: '300ms ease-in-out', hover: '150ms ease-out' } }, 'modern-dark': { colors: { primary: '#3b82f6', accent: '#60a5fa', background: '#0f172a', surface: '#1e293b', text: '#f1f5f9', textSecondary: '#94a3b8', border: '#334155', hover: '#475569', active: '#1e40af', disabled: '#64748b' }, typography: { fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif', fontSize: '14px', fontWeight: '500', lineHeight: '1.5' }, spacing: { padding: '12px 16px', margin: '4px 0', itemSpacing: '2px', levelIndent: '20px' }, borders: { radius: '8px', width: '1px', style: 'solid' }, shadows: { small: '0 1px 2px 0 rgb(0 0 0 / 0.3)', medium: '0 4px 6px -1px rgb(0 0 0 / 0.4)', large: '0 10px 15px -3px rgb(0 0 0 / 0.5)' }, animations: { duration: '200ms', easing: 'cubic-bezier(0.4, 0, 0.2, 1)', expandCollapse: '300ms ease-in-out', hover: '150ms ease-out' } }, 'glassmorphism': { colors: { primary: '#3b82f6', accent: '#60a5fa', background: 'rgba(255, 255, 255, 0.1)', surface: 'rgba(255, 255, 255, 0.2)', text: '#ffffff', textSecondary: 'rgba(255, 255, 255, 0.7)', border: 'rgba(255, 255, 255, 0.2)', hover: 'rgba(255, 255, 255, 0.1)', active: 'rgba(59, 130, 246, 0.3)', disabled: 'rgba(255, 255, 255, 0.3)' }, typography: { fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif', fontSize: '14px', fontWeight: '500', lineHeight: '1.5' }, spacing: { padding: '12px 16px', margin: '4px 0', itemSpacing: '2px', levelIndent: '20px' }, borders: { radius: '12px', width: '1px', style: 'solid' }, shadows: { small: '0 8px 32px 0 rgba(31, 38, 135, 0.37)', medium: '0 8px 32px 0 rgba(31, 38, 135, 0.37)', large: '0 8px 32px 0 rgba(31, 38, 135, 0.37)' }, animations: { duration: '300ms', easing: 'cubic-bezier(0.4, 0, 0.2, 1)', expandCollapse: '400ms ease-in-out', hover: '200ms ease-out' } }, 'classic-light': { // Classic theme styles... colors: { primary: '#1976d2', accent: '#2196f3', background: '#fafafa', surface: '#ffffff', text: '#212121', textSecondary: '#757575', border: '#e0e0e0', hover: '#f5f5f5', active: '#e3f2fd', disabled: '#bdbdbd' } // ... other properties }, 'classic-dark': { // Classic dark theme styles... colors: { primary: '#2196f3', accent: '#64b5f6', background: '#303030', surface: '#424242', text: '#ffffff', textSecondary: '#bdbdbd', border: '#616161', hover: '#525252', active: '#1976d2', disabled: '#757575' } // ... other properties }, 'minimal': { // Minimal theme styles... colors: { primary: '#000000', accent: '#333333', background: '#ffffff', surface: '#ffffff', text: '#000000', textSecondary: '#666666', border: '#e5e5e5', hover: '#f8f8f8', active: '#f0f0f0', disabled: '#cccccc' } // ... other properties }, 'custom': { // Will be overridden by customStyles colors: {}, typography: {}, spacing: {}, borders: {}, shadows: {}, animations: {} } }; // Computed styles based on current theme computedStyles = computed(() => { const theme = this.currentTheme(); const custom = this.customStyles(); if (theme === 'custom' && custom) { return custom; } const baseStyles = this.themes[theme]; // Merge with custom styles if provided if (custom) { return this.mergeStyles(baseStyles, custom); } return baseStyles; }); setTheme(theme) { this.currentTheme.set(theme); this.applyThemeToDocument(); } setCustomStyles(styles) { this.customStyles.set(styles); if (this.currentTheme() === 'custom') { this.applyThemeToDocument(); } } getThemeStyles(theme) { return this.themes[theme] || this.themes['modern-light']; } applyThemeToDocument() { const styles = this.computedStyles(); const root = this.document.documentElement; // Apply CSS custom properties if (styles.colors) { Object.entries(styles.colors).forEach(([key, value]) => { if (value) { root.style.setProperty(`--nav-color-${this.kebabCase(key)}`, value); } }); } if (styles.typography) { Object.entries(styles.typography).forEach(([key, value]) => { if (value) { root.style.setProperty(`--nav-typography-${this.kebabCase(key)}`, value); } }); } if (styles.spacing) { Object.entries(styles.spacing).forEach(([key, value]) => { if (value) { root.style.setProperty(`--nav-spacing-${this.kebabCase(key)}`, value); } }); } // Apply other style categories... } mergeStyles(base, custom) { return { colors: { ...base.colors, ...custom.colors }, typography: { ...base.typography, ...custom.typography }, spacing: { ...base.spacing, ...custom.spacing }, borders: { ...base.borders, ...custom.borders }, shadows: { ...base.shadows, ...custom.shadows }, animations: { ...base.animations, ...custom.animations } }; } kebabCase(str) { return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationThemeService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationThemeService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class NavigationRouteGeneratorService { /** * Generates Angular routes from navigation configuration */ generateRoutes(config) { const routes = []; if (config.rootComponent) { routes.push({ path: '', component: config.rootComponent, data: config.baseData, children: [ ...config.items.map(item => this.itemToRoute(item)), ...(config.defaultRoute ? [{ path: '', redirectTo: config.defaultRoute, pathMatch: 'full' }] : []) ] }); } else { routes.push(...config.items.map(item => this.itemToRoute(item))); if (config.defaultRoute) { routes.push({ path: '', redirectTo: config.defaultRoute, pathMatch: 'full' }); } } return routes; } /** * Converts a navigation item to an Angular route */ itemToRoute(item) { const route = { path: item.route, data: { ...item.data, navigationItem: item } }; // Handle different route types if (item.loadChildren) { route.loadChildren = item.loadChildren; } else if (item.component) { route.component = item.component; } // Handle redirects if (item.redirectTo) { route.redirectTo = item.redirectTo; if (item.routeMatch) { route.pathMatch = item.routeMatch; } } // Handle guards if (item.canActivate) { route.canActivate = item.canActivate; } if (item.guards?.canActivate) { route.canActivate = item.guards.canActivate; } if (item.guards?.canDeactivate) { route.canDeactivate = item.guards.canDeactivate; } // Handle resolvers if (item.resolve) { route.resolve = item.resolve; } // Handle children if (item.subItems && item.subItems.length > 0) { route.children = item.subItems.map(subItem => this.itemToRoute(subItem)); // Add default redirect for container routes if (item.routeType === 'container' || item.hasRootComponent) { route.children.unshift({ path: '', redirectTo: item.subItems[0].route, pathMatch: 'full' }); } } return route; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationRouteGeneratorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationRouteGeneratorService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationRouteGeneratorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class RouteBuilderUtils { /** * Construye la ruta completa para navegación basada en la configuración */ static buildNavigationRoute(item, parentPath = '') { const currentPath = parentPath ? `${parentPath}/${item.route}` : item.route; // Si es ruta raíz sin parent if (!parentPath) { return item.route; } return currentPath; } /** * Determina si un item es navegable (puede ser clickeado para navegar) */ static isNavigableItem(item) { // Si tiene component o loadChildren, es navegable if (item.component || item.loadChildren) { return true; } // Si tiene redirectTo, es navegable if (item.redirectTo) { return true; } // Si no tiene subitems, asumimos que es navegable if (!item.subItems || item.subItems.length === 0) { return true; } // Si tiene container type, también es navegable // if (item.routeType === 'container' || item.hasRootComponent || item.rootComponent) { // return true; // } return false; } /** * Construye el routerLink apropiado para el template */ static buildRouterLink(item, parentRoute) { if (item.redirectTo) { return item.redirectTo; } // Para items de primer nivel if (!parentRoute) { return `/${item.route}`; } // Para subitems return `/${parentRoute}/${item.route}`; } /** * Obtiene la ruta completa desde la raíz */ static getFullRoute(item, menuItems) { const pathParts = []; // Buscar el parent de este item const parent = this.findParentItem(item, menuItems); if (parent) { // Si el parent es container type, incluir su ruta if (parent.routeType === 'container' || parent.hasRootComponent || parent.rootComponent) { pathParts.push(parent.route); } } pathParts.push(item.route); return '/' + pathParts.join('/'); } /** * Encuentra el item padre de un subitem */ static findParentItem(targetItem, menuItems) { for (const item of menuItems) { if (item.subItems) { for (const subItem of item.subItems) { if (subItem === targetItem || subItem.id === targetItem.id) { return item; } // Búsqueda recursiva para subitems anidados const found = this.findParentItem(targetItem, item.subItems); if (found) return item; } } } return null; } } class NavigationItemComponent { item = input.required(); collapsed = input(false); level = input(0); parentRoute = input(); allMenuItems = input([]); navigationService = inject(NavigationService); expanded = signal(false); // Computed properties isNavigable = computed(() => { return RouteBuilderUtils.isNavigableItem(this.item()); }); routerLink = computed(() => { if (!this.isNavigable()) return null; if (this.isContainerType()) { return `/${this.item().route}`; } if (this.parentRoute()) { return `/${this.parentRoute()}/${this.item().route}`; } if (this.allMenuItems().length > 0) { return RouteBuilderUtils.getFullRoute(this.item(), this.allMenuItems()); } return `/${this.item().route}`; }); routerLinkOptions = computed(() => { return this.isContainerType() ? { exact: false } : { exact: true }; }); hasSubItems = computed(() => { const subItems = this.item().subItems; return subItems && subItems.length > 0; }); isContainerType = computed(() => { return this.item().routeType === 'container' || this.item().hasRootComponent || !!this.item().rootComponent; }); hasExpandableSubItems = computed(() => { return this.hasSubItems() && !this.isContainerType(); }); shouldShowSubItems = computed(() => { if (!this.hasSubItems() || this.collapsed()) return false; return this.expanded(); }); getParentRouteForSubItem = computed(() => { if (this.isContainerType()) { return this.item().route; } if (this.parentRoute()) { return `${this.parentRoute()}/${this.item().route}`; } return this.item().route; }); // Badge computed properties hasBadge = computed(() => { const badge = this.item().badge; return badge && (badge.text || badge.count !== undefined); }); badgeText = computed(() => { const badge = this.item().badge; if (!badge) return ''; return badge.text || (badge.count !== undefined ? badge.count.toString() : ''); }); badgeColor = computed(() => { return this.item().badge?.color || 'primary'; }); badgePosition = computed(() => { return this.item().badge?.position || 'top-right'; }); badgeSize = computed(() => { const level = this.level(); return level > 1 ? 'small' : 'medium'; }); // Style computed properties itemClasses = computed(() => { const classes = []; if (this.item().cssClass) { classes.push(this.item().cssClass); } if (this.item().disabled) { classes.push('disabled'); } if (this.level() > 0) { classes.push(`level-${this.level()}`); } if (this.expanded()) { classes.push('expanded'); } return classes.join(' '); }); itemStyles = computed(() => { const styles = { ...this.item().style }; // Dynamic padding based on level styles.paddingLeft = `${16 + (this.level() * 20)}px`; // Dynamic font size const baseSize = 14; const sizeDecrement = 1; const minSize = 12; const fontSize = Math.max(baseSize - (this.level() * sizeDecrement), minSize); styles.fontSize = `${fontSize}px`; return styles; }); subItemsStyles = computed(() => { return { borderLeft: `${Math.max(1, 3 - this.level())}px solid var(--nav-color-border)`, marginLeft: `${12 + (this.level() * 8)}px` }; }); iconOpacity = computed(() => { const baseOpacity = 1; const opacityDecrement = 0.1; const minOpacity = 0.6; return Math.max(baseOpacity - (this.level() * opacityDecrement), minOpacity); }); // Tooltip getTooltip = () => { return this.item().tooltip || (this.collapsed() ? this.item().label : ''); }; shouldShowTooltip = computed(() => { return this.collapsed() || !!this.item().tooltip; }); // Event handlers toggleExpanded(event) { if (event) { event.stopPropagation(); event.preventDefault(); } if (this.hasSubItems()) { this.expanded.update(current => !current); } } onItemClick() { if (this.item().disabled) return; // Handle external links if (this.item().external && this.item().route) { window.open(this.item().route, this.item().target || '_blank'); return; } // Let router handle the navigation this.navigationService.navigateToItem(this.item()); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: NavigationItemComponent, isStandalone: true, selector: "es-navigation-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, parentRoute: { classPropertyName: "parentRoute", publicName: "parentRoute", isSignal: true, isRequired: false, transformFunction: null }, allMenuItems: { classPropertyName: "allMenuItems", publicName: "allMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ` <!-- Navigable Item --> @if (isNavigable()) { <mat-list-item [routerLink]="routerLink()" routerLinkActive="active" [routerLinkActiveOptions]="routerLinkOptions()" [matTooltip]="getTooltip()" [matTooltipDisabled]="!shouldShowTooltip()" matTooltipPosition="right" class="nav-item navigable" [class]="itemClasses()" [style]="itemStyles()" (click)="onItemClick()"> <!-- Icon --> @if (item().icon) { <mat-icon matListItemIcon [style.opacity]="iconOpacity()" [matBadge]="badgeText()" [matBadgeColor]="badgeColor()" [matBadgeHidden]="!hasBadge() || badgePosition() !== 'icon'" [matBadgeSize]="badgeSize()"> {{ item().icon }} </mat-icon> } <!-- Label --> @if (!collapsed()) { <span matListItemTitle class="item-label"> {{ item().label }} <!-- Inline Badge --> @if (hasBadge() && badgePosition() === 'inline') { <span class="inline-badge" [class]="'badge-' + badgeColor()"> {{ badgeText() }} </span> } </span> } <!-- Expand Icon for Container Types --> @if (hasExpandableSubItems() && !collapsed()) { <mat-icon matListItemMeta class="expand-icon" (click)="toggleExpanded($event)"> {{ expanded() ? 'expand_less' : 'expand_more' }} </mat-icon> } <!-- Badge (Top-right position) --> @if (hasBadge() && badgePosition() === 'top-right' && !collapsed()) { <span class="top-right-badge" [class]="'badge-' + badgeColor()"> {{ badgeText() }} </span> } </mat-list-item> } @else { <!-- Non-navigable Item (Expandable) --> <mat-list-item (click)="toggleExpanded()" [matTooltip]="getTooltip()" [matTooltipDisabled]="!shouldShowTooltip()" matTooltipPosition="right" class="nav-item expandable" [class]="itemClasses()" [style]="itemStyles()"> <!-- Icon --> @if (item().icon) { <mat-icon matListItemIcon [style.opacity]="iconOpacity()"> {{ item().icon }} </mat-icon> } <!-- Label --> @if (!collapsed()) { <span matListItemTitle class="item-label">{{ item().label }}</span> <mat-icon matListItemMeta class="expand-icon"> {{ expanded() ? 'expand_less' : 'expand_more' }} </mat-icon> } </mat-list-item> } <!-- Sub-items --> @if (hasSubItems() && shouldShowSubItems()) { <div class="sub-items" [class.container-subitems]="isContainerType()" [style]="subItemsStyles()" @expandContractMenu> @for (subItem of item().subItems; track subItem.id || $index) { <es-navigation-item [item]="subItem" [collapsed]="collapsed()" [level]="level() + 1" [parentRoute]="getParentRouteForSubItem()" [allMenuItems]="allMenuItems()" /> } </div> } `, isInline: true, styles: [""], dependencies: [{ kind: "component", type: NavigationItemComponent, selector: "es-navigation-item", inputs: ["item", "collapsed", "level", "parentRoute", "allMenuItems"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2.MatListItemIcon, selector: "[matListItemIcon]" }, { kind: "directive", type: i2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i2.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i5.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }], animations: [ trigger('expandContractMenu', [ transition(':enter', [ style({ opacity: 0, height: '0px', overflow: 'hidden' }), animate('300ms ease-in-out', style({ opacity: 1, height: '*' })) ]), transition(':leave', [ style({ opacity: 1, height: '*', overflow: 'hidden' }), animate('300ms ease-in-out', style({ opacity: 0, height: '0px' })) ]) ]) ] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationItemComponent, decorators: [{ type: Component, args: [{ selector: 'es-navigation-item', standalone: true, imports: [ CommonModule, RouterModule, MatListModule, MatIconModule, MatButtonModule, MatTooltipModule, MatBadgeModule ], template: ` <!-- Navigable Item --> @if (isNavigable()) { <mat-list-item [routerLink]="routerLink()" routerLinkActive="active" [routerLinkActiveOptions]="routerLinkOptions()" [matTooltip]="getTooltip()" [matTooltipDisabled]="!shouldShowTooltip()" matTooltipPosition="right" class="nav-item navigable" [class]="itemClasses()" [style]="itemStyles()" (click)="onItemClick()"> <!-- Icon --> @if (item().icon) { <mat-icon matListItemIcon [style.opacity]="iconOpacity()" [matBadge]="badgeText()" [matBadgeColor]="badgeColor()" [matBadgeHidden]="!hasBadge() || badgePosition() !== 'icon'" [matBadgeSize]="badgeSize()"> {{ item().icon }} </mat-icon> } <!-- Label --> @if (!collapsed()) { <span matListItemTitle class="item-label"> {{ item().label }} <!-- Inline Badge --> @if (hasBadge() && badgePosition() === 'inline') { <span class="inline-badge" [class]="'badge-' + badgeColor()"> {{ badgeText() }} </span> } </span> } <!-- Expand Icon for Container Types --> @if (hasExpandableSubItems() && !collapsed()) { <mat-icon matListItemMeta class="expand-icon" (click)="toggleExpanded($event)"> {{ expanded() ? 'expand_less' : 'expand_more' }} </mat-icon> } <!-- Badge (Top-right position) --> @if (hasBadge() && badgePosition() === 'top-right' && !collapsed()) { <span class="top-right-badge" [class]="'badge-' + badgeColor()"> {{ badgeText() }} </span> } </mat-list-item> } @else { <!-- Non-navigable Item (Expandable) --> <mat-list-item (click)="toggleExpanded()" [matTooltip]="getTooltip()" [matTooltipDisabled]="!shouldShowTooltip()" matTooltipPosition="right" class="nav-item expandable" [class]="itemClasses()" [style]="itemStyles()"> <!-- Icon --> @if (item().icon) { <mat-icon matListItemIcon [style.opacity]="iconOpacity()"> {{ item().icon }} </mat-icon> } <!-- Label --> @if (!collapsed()) { <span matListItemTitle class="item-label">{{ item().label }}</span> <mat-icon matListItemMeta class="expand-icon"> {{ expanded() ? 'expand_less' : 'expand_more' }} </mat-icon> } </mat-list-item> } <!-- Sub-items --> @if (hasSubItems() && shouldShowSubItems()) { <div class="sub-items" [class.container-subitems]="isContainerType()" [style]="subItemsStyles()" @expandContractMenu> @for (subItem of item().subItems; track subItem.id || $index) { <es-navigation-item [item]="subItem" [collapsed]="collapsed()" [level]="level() + 1" [parentRoute]="getParentRouteForSubItem()" [allMenuItems]="allMenuItems()" /> } </div> } `, animations: [ trigger('expandContractMenu', [ transition(':enter', [ style({ opacity: 0, height: '0px', overflow: 'hidden' }), animate('300ms ease-in-out', style({ opacity: 1, height: '*' })) ]), transition(':leave', [ style({ opacity: 1, height: '*', overflow: 'hidden' }), animate('300ms ease-in-out', style({ opacity: 0, height: '0px' })) ]) ]) ] }] }] }); class NavigationHeaderComponent { config = input.required(); collapsed = input(false); toggleCollapse = output(); onUserMenuClick(menuItem) { if (menuItem.external && menuItem.route) { window.open(menuItem.route, menuItem.target || '_blank'); } else if (menuItem.action) { menuItem.action(); } // Add more navigation logic as needed } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: NavigationHeaderComponent, isStandalone: true, selector: "es-navigation-header", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleCollapse: "toggleCollapse" }, ngImport: i0, template: ` <div class="nav-header" [class.collapsed]="collapsed()"> <!-- Logo and Title --> @if (!collapsed()) { <div class="header-brand"> @if (config().logo) { <img [src]="config().logo" alt="Logo" class="logo"> } @if (config().title) { <h1 class="title">{{ config().title }}</h1> } </div> } @else { @if (config().logo) { <img [src]="config().logo" alt="Logo" class="logo-mini"> } } <!-- Header Actions --> @if (config().actions && (config().actions || []).length > 0) { <div class="header-actions" [class.collapsed]="collapsed()"> @for (action of config().actions; track $index) { <button mat-icon-button [matTooltip]="action.label" (click)="action.action()" class="header-action"> <mat-icon [matBadge]="action.badge?.text || ''" [matBadgeHidden]="!action.badge" [matBadgeColor]="'primary'"> {{ action.icon }} </mat-icon> </button> } </div> } <!-- User Profile --> @if (config().showUserProfile && config().userProfile && !collapsed()) { <div class="user-profile"> <button mat-button [matMenuTriggerFor]="userMenu" class="user-button"> @if (config().userProfile?.avatar) { <img [src]="config().userProfile!.avatar" alt="Avatar" class="avatar"> } @else { <mat-icon>account_circle</mat-icon> } <div class="user-info"> <span class="user-name">{{ config().userProfile?.name }}</span> @if (config().userProfile?.role) { <span class="user-role">{{ config().userProfile?.role }}</span> } </div> <mat-icon>expand_more</mat-icon> </button> <mat-menu #userMenu="matMenu"> @if (config().userProfile?.menu) { @for (menuItem of config().userProfile!.menu; track menuItem.id) { <button mat-menu-item (click)="onUserMenuClick(menuItem)"> @if (menuItem.icon) { <mat-icon>{{ menuItem.icon }}</mat-icon> } <span>{{ menuItem.label }}</span> </button> @if ($index < config().userProfile!.menu!.length - 1) { <mat-divider></mat-divider> } } } </mat-menu> </div> } <!-- Collapse Toggle --> <button mat-icon-button class="collapse-toggle-header" (click)="toggleCollapse.emit()" matTooltip="Toggle Navigation"> <mat-icon>menu</mat-icon> </button> </div> `, isInline: true, styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i3$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i2.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i5.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationHeaderComponent, decorators: [{ type: Component, args: [{ selector: 'es-navigation-header', standalone: true, imports: [ CommonModule, MatIconModule, MatButtonModule, MatMenuModule, MatDividerModule, MatTooltip, MatBadgeModule ], template: ` <div class="nav-header" [class.collapsed]="collapsed()"> <!-- Logo and Title --> @if (!collapsed()) { <div class="header-brand"> @if (config().logo) { <img [src]="config().logo" alt="Logo" class="logo"> } @if (config().title) { <h1 class="title">{{ config().title }}</h1> } </div> } @else { @if (config().logo) { <img [src]="config().logo" alt="Logo" class="logo-mini"> } } <!-- Header Actions --> @if (config().actions && (config().actions || []).length > 0) { <div class="header-actions" [class.collapsed]="collapsed()"> @for (action of config().actions; track $index) { <button mat-icon-button [matTooltip]="action.label" (click)="action.action()" class="header-action"> <mat-icon [matBadge]="action.badge?.text || ''" [matBadgeHidden]="!action.badge" [matBadgeColor]="'primary'"> {{ action.icon }} </mat-icon> </button> } </div> } <!-- User Profile --> @if (config().showUserProfile && config().userProfile && !collapsed()) { <div class="user-profile"> <button mat-button [matMenuTriggerFor]="userMenu" class="user-button"> @if (config().userProfile?.avatar) { <img [src]="config().userProfile!.avatar" alt="Avatar" class="avatar"> } @else { <mat-icon>account_circle</mat-icon> } <div class="user-info"> <span class="user-name">{{ config().userProfile?.name }}</span> @if (config().userProfile?.role) { <span class="user-role">{{ config().userProfile?.role }}</span> } </div> <mat-icon>expand_more</mat-icon> </button> <mat-menu #userMenu="matMenu"> @if (config().userProfile?.menu) { @for (menuItem of config().userProfile!.menu; track menuItem.id) { <button mat-menu-item (click)="onUserMenuClick(menuItem)"> @if (menuItem.icon) { <mat-icon>{{ menuItem.icon }}</mat-icon> } <span>{{ menuItem.label }}</span> </button> @if ($index < config().userProfile!.menu!.length - 1) { <mat-divider></mat-divider> } } } </mat-menu> </div> } <!-- Collapse Toggle --> <button mat-icon-button class="collapse-toggle-header" (click)="toggleCollapse.emit()" matTooltip="Toggle Navigation"> <mat-icon>menu</mat-icon> </button> </div> ` }] }] }); class NavigationFooterComponent { config = input.required(); collapsed = input(false); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NavigationFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: NavigationFooterComponent, isStandalone: true, selector: "es-navigation-footer", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, tran