@angulogic/ng-sidebar
Version:
angular sidebar
1 lines • 75 kB
Source Map (JSON)
{"version":3,"file":"angulogic-ng-sidebar.mjs","sources":["../../../projects/ng-sidebar/src/lib/ng-sidebar.service.ts","../../../projects/ng-sidebar/src/lib/component/al-icon/al-icon.component.ts","../../../projects/ng-sidebar/src/lib/toggler.directive.ts","../../../projects/ng-sidebar/src/lib/component/theme-toggler/theme-toggler.component.ts","../../../projects/ng-sidebar/src/lib/component/theme-toggler/theme-toggler.component.html","../../../projects/ng-sidebar/src/lib/component/ng-sidebar.component.ts","../../../projects/ng-sidebar/src/lib/component/ng-sidebar.component.html","../../../projects/ng-sidebar/src/lib/ng-sidebar.module.ts","../../../projects/ng-sidebar/src/public-api.ts","../../../projects/ng-sidebar/src/angulogic-ng-sidebar.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\r\nimport {\r\n ExpandClickEvent,\r\n MenuData,\r\n ResizeEvent,\r\n SidebarData,\r\n SidebarModel,\r\n} from './sidebar.model';\r\nimport { NavigationEnd, Router } from '@angular/router';\r\nimport { HttpClient } from '@angular/common/http';\r\n\r\n/**\r\n * Service responsible for managing sidebar state, configurations, and behaviors.\r\n * It handles sidebar initialization, resizing, menu interactions, and theme changes.\r\n *\r\n * @export\r\n * @class NgSidebarService\r\n */\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class NgSidebarService {\r\n /**\r\n * Tracks whether auto-positioning is enabled.\r\n * @default false\r\n */\r\n autoPositionActive: boolean = false;\r\n\r\n /**\r\n * Indicates whether the sidebar is currently being resized.\r\n * @default false\r\n */\r\n isResizing: boolean = false;\r\n\r\n /**\r\n * Stores the complete sidebar configuration and data.\r\n */\r\n sidebarData!: SidebarModel;\r\n\r\n /**\r\n * MutationObserver to monitor sidebar style changes for auto-positioning.\r\n */\r\n private observer!: MutationObserver;\r\n\r\n /**\r\n * Initializes the sidebar service and listens for route changes.\r\n *\r\n * @param {Router} router - Angular Router for detecting navigation events.\r\n * @param {HttpClient} http - Angular HttpClient for loading assets (e.g., SVG icons).\r\n */\r\n constructor(\r\n public router: Router,\r\n private http: HttpClient\r\n ) {\r\n router.events.subscribe(route => {\r\n if (route instanceof NavigationEnd) {\r\n this.sidebarData.sidebarData.forEach(data => {\r\n this.updateActiveState(data.data, route.url);\r\n });\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Initializes the sidebar configuration with default values if not provided.\r\n *\r\n * @param {Partial<SidebarModel> & { sidebarData: SidebarData[] }} data - Sidebar data with optional configurations.\r\n * @returns {SidebarModel} - The complete SidebarModel with default values applied.\r\n */\r\n initilazeSidebarData(\r\n data: Partial<SidebarModel> & { sidebarData: SidebarData[] }\r\n ): SidebarModel {\r\n // Banner options\r\n data.bannerOptions = data.bannerOptions\r\n ? {\r\n logo: data.bannerOptions.logo ?? 'assets/icons/angular-logo.png',\r\n title: data.bannerOptions.title ?? 'Angulogic',\r\n onClick: data.bannerOptions.onClick,\r\n }\r\n : undefined;\r\n\r\n // User options\r\n data.userOptions = data.userOptions\r\n ? {\r\n avatar: data.userOptions.avatar ?? 'assets/icons/avatar.svg',\r\n name: data.userOptions.name,\r\n position: data.userOptions.position ?? 'bottom',\r\n onClick: data.userOptions.onClick,\r\n }\r\n : undefined;\r\n\r\n // Search options\r\n data.searchOptions = data.searchOptions\r\n ? {\r\n placeholder: data.searchOptions.placeholder,\r\n caseSensitive: data.searchOptions.caseSensitive ?? false,\r\n strategy: data.searchOptions.strategy ?? 'contains',\r\n cssClass: data.searchOptions.cssClass,\r\n localCompare: data.searchOptions.localCompare ?? 'en',\r\n onSearchStart: data.searchOptions.onSearchStart,\r\n onSearchEnd: data.searchOptions.onSearchEnd,\r\n }\r\n : undefined;\r\n\r\n // Sidebar data initialization\r\n data.sidebarData = data.sidebarData.map(item => ({\r\n title: item.title,\r\n cssClass: item.cssClass,\r\n visible: item.visible ?? true,\r\n data: this.initializeMenuData(item.data),\r\n }));\r\n\r\n // Sidebar options initialization\r\n data.options = data.options\r\n ? {\r\n resize: data.options.resize ?? true,\r\n expand: data.options.expand ?? true,\r\n favorites: data.options.favorites ?? true,\r\n favoritesTitle: data.options.favoritesTitle ?? 'Favorites',\r\n search: data.options.search ?? true,\r\n cssClass: data.options.cssClass ?? '',\r\n viewMode: data.options.viewMode ?? 'toggle',\r\n theme: data.options.theme ?? 'light',\r\n themePicker: data.options.themePicker ?? true,\r\n minWidth: data.options.minWidth ?? 300,\r\n maxWidth: data.options.maxWidth ?? 500,\r\n width: data.options.width ?? 300,\r\n themeText: data.options.themeText ?? { light: 'Light', dark: 'Dark' },\r\n autoPosition: data.options.autoPosition ?? true,\r\n toggleCollapseIcon:\r\n data.options.toggleCollapseIcon ?? 'assets/icons/collapse.svg',\r\n toggleExpandIcon:\r\n data.options.toggleExpandIcon ?? 'assets/icons/expand.svg',\r\n pinIcon: data.options.pinIcon ?? 'assets/icons/pin.svg',\r\n unpinIcon: data.options.unpinIcon ?? 'assets/icons/unpin.svg',\r\n closeIcon: data.options.closeIcon ?? 'assets/icons/cancel.svg',\r\n pinned:\r\n data.options.pinned ??\r\n (data.options.expand && data.options.viewMode === 'hover') ??\r\n false,\r\n onThemeChange: data.options.onThemeChange,\r\n onResizeStart: data.options.onResizeStart,\r\n onResizing: data.options.onResizing,\r\n onResizeEnd: data.options.onResizeEnd,\r\n onExpand: data.options.onExpand,\r\n onCollapse: data.options.onCollapse,\r\n onMenuNodeClick: data.options.onMenuNodeClick,\r\n }\r\n : {\r\n resize: true,\r\n expand: true,\r\n favorites: true,\r\n search: true,\r\n viewMode: 'toggle',\r\n theme: 'light',\r\n };\r\n\r\n this.sidebarData = data as SidebarModel;\r\n return data as SidebarModel;\r\n }\r\n\r\n /**\r\n * Enables automatic positioning of the sidebar.\r\n * It observes the sidebar's width and updates the CSS variable `--sidebar-width` dynamically.\r\n */\r\n setAutoPosition(): void {\r\n const divElement = document.getElementById('ng-sidebar');\r\n if (!divElement) {\r\n return;\r\n }\r\n\r\n if (!document.body.classList.contains('auto-position')) {\r\n document.body.classList.add('auto-position');\r\n }\r\n\r\n const duration = 300;\r\n this.observer = new MutationObserver(() => {\r\n const width = divElement.offsetWidth;\r\n this.updateWidth(divElement, performance.now(), duration);\r\n document.documentElement.style.setProperty(\r\n '--sidebar-width',\r\n `${width}px`\r\n );\r\n });\r\n\r\n this.observer.observe(divElement, {\r\n attributeFilter: ['style'],\r\n });\r\n\r\n this.autoPositionActive = true;\r\n console.log('Auto position enabled');\r\n }\r\n\r\n /**\r\n * Animates the width update of the sidebar.\r\n *\r\n * @param {HTMLElement} divElement - The sidebar element whose width is being updated.\r\n * @param {number} startTime - The start time of the animation.\r\n * @param {number} duration - The duration of the width animation in milliseconds.\r\n */\r\n updateWidth(\r\n divElement: HTMLElement,\r\n startTime: number,\r\n duration: number\r\n ): void {\r\n const animateWidth = (timestamp: number) => {\r\n let progress = (timestamp - startTime) / duration;\r\n const currentWidth = divElement.offsetWidth;\r\n document.documentElement.style.setProperty(\r\n '--sidebar-width',\r\n `${currentWidth}px`\r\n );\r\n if (progress < 1) {\r\n requestAnimationFrame(animateWidth);\r\n }\r\n };\r\n requestAnimationFrame(animateWidth);\r\n }\r\n\r\n /**\r\n * Disables automatic positioning of the sidebar.\r\n * Stops observing style changes and resets CSS modifications.\r\n */\r\n destroyAutoPosition(): void {\r\n this.observer.disconnect();\r\n if (document.body.classList.contains('auto-position')) {\r\n document.body.classList.remove('auto-position');\r\n }\r\n this.autoPositionActive = false;\r\n console.log('Auto position disabled');\r\n }\r\n\r\n /**\r\n * Handles sidebar resizing using mouse events.\r\n *\r\n * @param {SidebarModel} sidebarData - The sidebar configuration object.\r\n */\r\n resize(sidebarData: SidebarModel): void {\r\n this.isResizing = true;\r\n const initialPin = sidebarData.options.pinned;\r\n sidebarData.options.pinned = true;\r\n\r\n const startEvent: ResizeEvent = {\r\n cancel: false,\r\n sidebarOptions: sidebarData,\r\n };\r\n\r\n if (sidebarData.options.onResizeStart) {\r\n sidebarData.options.onResizeStart(startEvent);\r\n if (startEvent.cancel) {\r\n this.isResizing = false;\r\n return;\r\n }\r\n }\r\n\r\n document.body.classList.add('no-select');\r\n\r\n /**\r\n * Handles mouse movement during resizing.\r\n *\r\n * @param {MouseEvent} e - The mouse movement event.\r\n */\r\n const mouseMoveListener = (e: MouseEvent) => {\r\n const resizeEvent: ResizeEvent = {\r\n cancel: false,\r\n sidebarOptions: sidebarData,\r\n mouseEvent: e,\r\n };\r\n\r\n if (sidebarData.options.onResizing) {\r\n sidebarData.options.onResizing(resizeEvent);\r\n if (resizeEvent.cancel) {\r\n return;\r\n }\r\n }\r\n\r\n if (\r\n sidebarData.options.minWidth &&\r\n e.clientX < sidebarData.options.minWidth\r\n ) {\r\n sidebarData.options.width = sidebarData.options.minWidth;\r\n } else if (\r\n sidebarData.options.maxWidth &&\r\n e.clientX > sidebarData.options.maxWidth\r\n ) {\r\n sidebarData.options.width = sidebarData.options.maxWidth;\r\n } else {\r\n sidebarData.options.width = e.clientX;\r\n }\r\n };\r\n\r\n document.addEventListener('mousemove', mouseMoveListener);\r\n\r\n /**\r\n * Handles mouse release after resizing.\r\n *\r\n * @param {MouseEvent} e - The mouse up event.\r\n */\r\n const mouseUpListener = (e: MouseEvent) => {\r\n document.body.classList.remove('no-select');\r\n document.removeEventListener('mousemove', mouseMoveListener);\r\n document.removeEventListener('mouseup', mouseUpListener);\r\n\r\n this.isResizing = false;\r\n sidebarData.options.pinned = initialPin;\r\n\r\n const endEvent: ResizeEvent = {\r\n sidebarOptions: sidebarData,\r\n mouseEvent: e,\r\n };\r\n\r\n if (sidebarData.options.onResizeEnd) {\r\n sidebarData.options.onResizeEnd(endEvent);\r\n }\r\n };\r\n\r\n document.addEventListener('mouseup', mouseUpListener);\r\n }\r\n\r\n /**\r\n * Initializes menu data by assigning default values if not provided.\r\n *\r\n * @param {MenuData[]} menuData - The menu data array to initialize.\r\n * @returns {MenuData[]} - The processed menu data with default values applied.\r\n */\r\n private initializeMenuData(menuData: MenuData[]): MenuData[] {\r\n return menuData.map(item => {\r\n item['name'] = item['name'];\r\n item['icon'] = item['icon'];\r\n item['route'] = item['route'];\r\n item['visible'] = item['visible'] ?? true;\r\n item['disabled'] = item['disabled'] ?? false;\r\n item['isExpanded'] = item['isExpanded'] ?? false;\r\n item['isFavorited'] = item['isFavorited'] ?? false;\r\n item['cssClass'] = item['cssClass'];\r\n item['active'] = item['active'] ?? false;\r\n item['children'] = item['children']\r\n ? this.initializeMenuData(item['children'])\r\n : undefined;\r\n item['onClick'] = item['onClick'];\r\n return item;\r\n });\r\n }\r\n\r\n /**\r\n * Searches for menu items by name within the sidebar data.\r\n *\r\n * @param {SidebarModel} data - The sidebar configuration containing menu data.\r\n * @param {string} searchValue - The search query entered by the user.\r\n * @returns {MenuData[]} - An array of matching menu items.\r\n */\r\n searchByName(data: SidebarModel, searchValue: string): MenuData[] {\r\n const { searchOptions, sidebarData } = data;\r\n\r\n /**\r\n * Compares two strings based on the configured search options.\r\n *\r\n * @param {string} source - The original menu item name.\r\n * @param {string} target - The search query entered by the user.\r\n * @returns {boolean} - True if the search criteria match, false otherwise.\r\n */\r\n const compareStrings = (source: string, target: string): boolean => {\r\n if (!searchOptions?.caseSensitive) {\r\n source = source.toLocaleLowerCase(searchOptions?.localCompare);\r\n target = target.toLocaleLowerCase(searchOptions?.localCompare);\r\n }\r\n switch (searchOptions?.strategy) {\r\n case 'contains':\r\n return source.includes(target);\r\n case 'startsWith':\r\n return source.startsWith(target);\r\n case 'endsWith':\r\n return source.endsWith(target);\r\n case 'equal':\r\n return source === target;\r\n default:\r\n return false;\r\n }\r\n };\r\n\r\n /**\r\n * Recursively searches menu items and their children.\r\n *\r\n * @param {MenuData[]} data - The menu items to search in.\r\n * @returns {MenuData[]} - The filtered menu items.\r\n */\r\n const searchInMenuData = (data: MenuData[]): MenuData[] => {\r\n const resultSet = new Set<MenuData>();\r\n data.forEach(item => {\r\n if (compareStrings(item.name, searchValue)) {\r\n resultSet.add(item);\r\n }\r\n if (item.children && item.children.length > 0) {\r\n const childResults = searchInMenuData(item.children);\r\n if (childResults.length > 0) {\r\n item.children = childResults;\r\n resultSet.add(item);\r\n }\r\n }\r\n });\r\n\r\n return Array.from(resultSet);\r\n };\r\n\r\n return sidebarData.flatMap(sidebarItem =>\r\n searchInMenuData(sidebarItem.data)\r\n );\r\n }\r\n\r\n /**\r\n * Updates the active state of menu items based on the current route.\r\n *\r\n * @param {MenuData[]} menuData - The menu data array to update.\r\n * @param {string} currentRoute - The current active route.\r\n */\r\n updateActiveState(menuData: MenuData[], currentRoute: string): void {\r\n menuData.forEach(item => {\r\n if (item.route?.startsWith('/')) {\r\n item.active = item.route === currentRoute;\r\n } else {\r\n item.active = `/${item.route}` === currentRoute;\r\n }\r\n\r\n if (item.children) {\r\n this.updateActiveState(item.children, currentRoute);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Toggles the sidebar theme between 'light' and 'dark'.\r\n * Updates the `theme` property in `sidebarData.options`.\r\n */\r\n changeTheme(): void {\r\n if (this.sidebarData.options.theme === 'light') {\r\n this.sidebarData.options.theme = 'dark';\r\n } else {\r\n this.sidebarData.options.theme = 'light';\r\n }\r\n\r\n // Trigger theme change event if provided\r\n if (this.sidebarData.options.onThemeChange) {\r\n this.sidebarData.options.onThemeChange(this.sidebarData.options.theme);\r\n }\r\n }\r\n\r\n /**\r\n * Loads an SVG file from the given path.\r\n *\r\n * @param {string} path - The file path of the SVG to load.\r\n * @returns {Promise<string>} - A promise resolving to the SVG content.\r\n */\r\n loadSvg(path: string): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n this.http.get(path, { responseType: 'text' }).subscribe({\r\n next: svgContent => resolve(svgContent),\r\n error: err =>\r\n reject(\r\n new Error(`Failed to load SVG: ${path}. Error: ${err.message}`)\r\n ),\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Toggles the sidebar's expanded or collapsed state.\r\n * Triggers the appropriate expand/collapse event if provided.\r\n */\r\n async toggleSidebar(): Promise<void> {\r\n let event: ExpandClickEvent = {\r\n cancel: false,\r\n click: true,\r\n };\r\n\r\n // Trigger collapse event if sidebar is currently expanded\r\n if (this.sidebarData.options.expand) {\r\n await Promise.resolve(this.sidebarData.options.onCollapse?.(event));\r\n }\r\n // Trigger expand event if sidebar is currently collapsed\r\n else {\r\n await Promise.resolve(this.sidebarData.options.onExpand?.(event));\r\n }\r\n\r\n // If the event was canceled, do not toggle\r\n if (event.cancel) return;\r\n\r\n // Toggle expand/collapse state\r\n this.sidebarData.options.expand = !this.sidebarData.options.expand;\r\n }\r\n}\r\n","import { Component, HostBinding, Input } from '@angular/core';\r\nimport { SafeHtml, DomSanitizer } from '@angular/platform-browser';\r\nimport { NgSidebarService } from '../../ng-sidebar.service';\r\n\r\n/**\r\n * A component that dynamically loads and displays SVG icons or image sources.\r\n * If an SVG file is provided, it fetches and sanitizes it to prevent security risks.\r\n * Otherwise, it renders an image element with the given source.\r\n *\r\n * @export\r\n * @class AlIconComponent\r\n */\r\n@Component({\r\n selector: 'al-icon',\r\n template: ``,\r\n host: {\r\n class: 'al-icon',\r\n style: 'display: flex;',\r\n },\r\n})\r\nexport class AlIconComponent {\r\n /**\r\n * Stores the sanitized SVG content or image element.\r\n * The content is directly bound to the component's `innerHTML`.\r\n */\r\n @HostBinding('innerHTML') safeSvg: SafeHtml | undefined;\r\n\r\n /**\r\n * Stores the image source URL when an image (non-SVG) is provided.\r\n */\r\n imgSource?: string;\r\n\r\n /**\r\n * Sets the source of the icon. If an SVG file is detected, it is loaded and sanitized.\r\n * Otherwise, an image element is created.\r\n *\r\n * @param {string | undefined} content - The source URL or inline SVG content.\r\n */\r\n @Input() set icon(content: string | undefined) {\r\n if (!content) return;\r\n\r\n // Load and sanitize SVG content\r\n if (content.endsWith('.svg')) {\r\n this.sidebarService\r\n .loadSvg(content)\r\n .then(svg => {\r\n this.safeSvg = this.sanitizer.bypassSecurityTrustHtml(svg);\r\n })\r\n .catch(error => {\r\n console.error('Error loading SVG:', error);\r\n this.safeSvg = undefined;\r\n });\r\n } else {\r\n // Render as an <img> tag\r\n this.safeSvg = this.sanitizer.bypassSecurityTrustHtml(\r\n `<img src=\"${content}\" />`\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Creates an instance of `AlIconComponent`.\r\n *\r\n * @param {DomSanitizer} sanitizer - Angular's sanitizer service to bypass security restrictions.\r\n * @param {NgSidebarService} sidebarService - The sidebar service used to fetch SVG content.\r\n */\r\n constructor(\r\n private sanitizer: DomSanitizer,\r\n private sidebarService: NgSidebarService\r\n ) {}\r\n}\r\n","import { Directive, HostListener } from '@angular/core';\r\nimport { NgSidebarService } from './ng-sidebar.service';\r\n\r\n/**\r\n * A directive that toggles the sidebar when the attached element is clicked.\r\n *\r\n * @export\r\n * @class TogglerDirective\r\n */\r\n@Directive({\r\n selector: '[sidebarToggler]',\r\n})\r\nexport class TogglerDirective {\r\n /**\r\n * Creates an instance of `TogglerDirective`.\r\n *\r\n * @param {NgSidebarService} sidebarService - The sidebar service that manages sidebar state.\r\n */\r\n constructor(private sidebarService: NgSidebarService) {}\r\n\r\n /**\r\n * Listens for click events on the host element and toggles the sidebar.\r\n */\r\n @HostListener('click')\r\n toggleSidebar(): void {\r\n this.sidebarService.toggleSidebar();\r\n }\r\n}\r\n","import { Component } from '@angular/core';\nimport { NgSidebarService } from '../../ng-sidebar.service';\n\n/**\n * A component that toggles the theme of the sidebar between 'light' and 'dark'.\n *\n * @export\n * @class ThemeTogglerComponent\n */\n@Component({\n selector: 'al-theme-toggler',\n templateUrl: './theme-toggler.component.html',\n styleUrl: './theme-toggler.component.scss',\n})\nexport class ThemeTogglerComponent {\n /**\n * Creates an instance of `ThemeTogglerComponent`.\n *\n * @param {NgSidebarService} sidebarService - The sidebar service used to change the theme.\n */\n constructor(private sidebarService: NgSidebarService) {}\n\n /**\n * Toggles the sidebar theme between 'light' and 'dark'.\n * Calls the `changeTheme()` method from `NgSidebarService`.\n */\n toggleTheme(): void {\n this.sidebarService.changeTheme();\n }\n}\n","<div class=\"theme-toggler\">\n <svg display=\"none\">\n <symbol id=\"light\" viewBox=\"0 0 24 24\">\n <g stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(0,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(45,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(90,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(135,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(180,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(225,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(270,12,12)\" />\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"20\" transform=\"rotate(315,12,12)\" />\n </g>\n <circle fill=\"currentColor\" cx=\"12\" cy=\"12\" r=\"5\" />\n </symbol>\n <symbol id=\"dark\" viewBox=\"0 0 24 24\">\n <path\n fill=\"currentColor\"\n d=\"M15.1,14.9c-3-0.5-5.5-3-6-6C8.8,7.1,9.1,5.4,9.9,4c0.4-0.8-0.4-1.7-1.2-1.4C4.6,4,1.8,7.9,2,12.5c0.2,5.1,4.4,9.3,9.5,9.5c4.5,0.2,8.5-2.6,9.9-6.6c0.3-0.8-0.6-1.7-1.4-1.2C18.6,14.9,16.9,15.2,15.1,14.9z\" />\n </symbol>\n </svg>\n <label class=\"switch\">\n <input\n class=\"switch__input\"\n (change)=\"toggleTheme()\"\n type=\"checkbox\"\n role=\"switch\"\n name=\"dark\" />\n <svg class=\"switch__icon\" width=\"24px\" height=\"24px\" aria-hidden=\"true\">\n <use href=\"#light\" />\n </svg>\n <svg class=\"switch__icon\" width=\"24px\" height=\"24px\" aria-hidden=\"true\">\n <use href=\"#dark\" />\n </svg>\n <span class=\"switch__inner\"></span>\n <span class=\"switch__inner-icons\">\n <svg class=\"switch__icon\" width=\"24px\" height=\"24px\" aria-hidden=\"true\">\n <use href=\"#light\" />\n </svg>\n <svg class=\"switch__icon\" width=\"24px\" height=\"24px\" aria-hidden=\"true\">\n <use href=\"#dark\" />\n </svg>\n </span>\n </label>\n</div>\n","import {\r\n Component,\r\n DoCheck,\r\n HostBinding,\r\n HostListener,\r\n Input,\r\n OnInit,\r\n TemplateRef,\r\n} from '@angular/core';\r\nimport {\r\n MenuClickEvent,\r\n MenuData,\r\n SearchEndEvent,\r\n SearchStartEvent,\r\n SidebarData,\r\n SidebarModel,\r\n} from '../sidebar.model';\r\nimport { NgSidebarService } from '../ng-sidebar.service';\r\n\r\n/**\r\n * A dynamic and interactive sidebar component for Angular applications.\r\n * It supports features such as menu navigation, search, favorites, and theme management.\r\n *\r\n * @export\r\n * @class NgSidebarComponent\r\n * @implements {DoCheck}\r\n * @implements {OnInit}\r\n */\r\n@Component({\r\n selector: 'ng-sidebar',\r\n templateUrl: './ng-sidebar.component.html',\r\n styleUrls: ['./ng-sidebar.component.scss'],\r\n})\r\nexport class NgSidebarComponent implements DoCheck, OnInit {\r\n /**\r\n * Stores the current sidebar configuration.\r\n */\r\n sidebarData!: SidebarModel;\r\n\r\n /**\r\n * Stores the original sidebar data for deep cloning.\r\n */\r\n SIDEBAR_DATA!: SidebarModel;\r\n\r\n /**\r\n * Stores the list of favorite menu items.\r\n */\r\n favorites: MenuData[] = [];\r\n\r\n /**\r\n * Sets the sidebar options and initializes the sidebar data.\r\n * Ensures the default theme is applied if not provided.\r\n *\r\n * @param {SidebarModel} val - The sidebar configuration model.\r\n */\r\n @Input({ required: true }) set options(val: SidebarModel) {\r\n this.sidebarData = this.ngSidebarService.initilazeSidebarData(val);\r\n if (!val.options.theme) {\r\n val.options.theme = 'light';\r\n }\r\n this.SIDEBAR_DATA = this.deepClone(this.sidebarData);\r\n }\r\n\r\n /**\r\n * Defines a custom template for menu node content.\r\n */\r\n @Input() nodeContent?: TemplateRef<any>;\r\n\r\n /**\r\n * Adds the `.ng-sidebar` class to the component.\r\n */\r\n @HostBinding('class.ng-sidebar') ngSidebarClass = true;\r\n\r\n /**\r\n * Applies the `.collapsed` class when the sidebar is collapsed.\r\n */\r\n @HostBinding('class.collapsed') get isCollapsed() {\r\n return !this.sidebarData.options.expand;\r\n }\r\n\r\n /**\r\n * Applies the `.transition` class when the sidebar is transitioning.\r\n */\r\n @HostBinding('class.transition') get isTransition() {\r\n return !this.ngSidebarService.isResizing;\r\n }\r\n\r\n /**\r\n * Applies the `.al-dark-theme` class when the dark theme is active.\r\n */\r\n @HostBinding('class.al-dark-theme') get isDarkTheme() {\r\n return this.sidebarData.options.theme === 'dark';\r\n }\r\n\r\n /**\r\n * Binds the sidebar's width dynamically based on its expanded state.\r\n */\r\n @HostBinding('style.width') get sidebarWidth() {\r\n return this.sidebarData.options.expand\r\n ? `${this.sidebarData.options.width}px`\r\n : this.sidebarData.options.viewMode === 'mobile'\r\n ? '0px'\r\n : 'var(--collapse-width)';\r\n }\r\n\r\n /**\r\n * Binds the sidebar's max width dynamically.\r\n */\r\n @HostBinding('style.maxWidth') get sidebarMaxWidth() {\r\n return this.sidebarData.options.expand\r\n ? `${this.sidebarData.options.maxWidth}px`\r\n : 'unset';\r\n }\r\n\r\n /**\r\n * Binds the sidebar's min width dynamically.\r\n */\r\n @HostBinding('style.minWidth') get sidebarMinWidth() {\r\n return this.sidebarData.options.expand\r\n ? `${this.sidebarData.options.minWidth}px`\r\n : 'unset';\r\n }\r\n\r\n /**\r\n * Creates an instance of `NgSidebarComponent`.\r\n *\r\n * @param {NgSidebarService} ngSidebarService - The sidebar service for handling state and actions.\r\n */\r\n constructor(public ngSidebarService: NgSidebarService) {}\r\n\r\n /**\r\n * Lifecycle hook that runs when the component initializes.\r\n * It updates the list of favorite menu items.\r\n */\r\n ngOnInit(): void {\r\n this.updateFavorites();\r\n }\r\n\r\n /**\r\n * Lifecycle hook that detects changes and manages auto-positioning.\r\n */\r\n ngDoCheck(): void {\r\n if (\r\n this.sidebarData.options.autoPosition !==\r\n this.ngSidebarService.autoPositionActive\r\n ) {\r\n this.sidebarData.options.autoPosition\r\n ? this.ngSidebarService.setAutoPosition()\r\n : this.ngSidebarService.destroyAutoPosition();\r\n }\r\n }\r\n\r\n /**\r\n * Handles click events on the banner elements (logo or title).\r\n * Triggers the `onClick` event if it exists in `bannerOptions`.\r\n *\r\n * @param {'logo' | 'title'} element - The clicked banner element.\r\n */\r\n protected onBannerClick(element: 'logo' | 'title'): void {\r\n this.sidebarData.bannerOptions?.onClick?.(element);\r\n }\r\n\r\n /**\r\n * Handles click events on the user profile elements (avatar or name).\r\n * Triggers the `onClick` event if it exists in `userOptions`.\r\n *\r\n * @param {'avatar' | 'name'} element - The clicked user profile element.\r\n */\r\n protected onUserClick(element: 'avatar' | 'name'): void {\r\n this.sidebarData.userOptions?.onClick?.(element);\r\n }\r\n\r\n /**\r\n * Handles search input events and filters the sidebar menu items.\r\n * Triggers `onSearchStart` and `onSearchEnd` events if provided.\r\n *\r\n * @param {KeyboardEvent} event - The keyboard event triggered by the search input.\r\n */\r\n protected async onSearch(event: KeyboardEvent): Promise<void> {\r\n const element = event.currentTarget as HTMLInputElement;\r\n const searchValue = element.value.trim();\r\n\r\n let searchStartEvent: SearchStartEvent = {\r\n nativeElement: element,\r\n searchValue: searchValue,\r\n cancel: false,\r\n };\r\n\r\n if (this.sidebarData.searchOptions?.onSearchStart) {\r\n await Promise.resolve(\r\n this.sidebarData.searchOptions.onSearchStart(searchStartEvent)\r\n );\r\n }\r\n\r\n if (!searchStartEvent.cancel) {\r\n let filteredResults: MenuData[] = [];\r\n\r\n if (searchValue.length > 0) {\r\n filteredResults = this.ngSidebarService.searchByName(\r\n this.deepClone(this.SIDEBAR_DATA),\r\n searchValue\r\n );\r\n\r\n if (filteredResults.length > 0) {\r\n this.sidebarData.sidebarData = this.SIDEBAR_DATA.sidebarData.map(\r\n sidebarItem => {\r\n const matchingItems = filteredResults.filter(item =>\r\n sidebarItem.data.some(dataItem => dataItem.name === item.name)\r\n );\r\n\r\n const updateExpandedState = (item: MenuData) => {\r\n item.isExpanded = true;\r\n if (item.children) {\r\n item.children.forEach(updateExpandedState);\r\n }\r\n };\r\n\r\n matchingItems.forEach(updateExpandedState);\r\n\r\n return {\r\n ...sidebarItem,\r\n data: [...new Set(matchingItems)],\r\n };\r\n }\r\n );\r\n\r\n this.sidebarData.sidebarData = this.sidebarData.sidebarData.filter(\r\n d => d.data.length > 0\r\n );\r\n } else {\r\n this.sidebarData.sidebarData = [];\r\n }\r\n } else {\r\n this.sidebarData = this.deepClone(this.SIDEBAR_DATA);\r\n this.ngSidebarService.sidebarData = this.sidebarData;\r\n }\r\n\r\n if (this.sidebarData.searchOptions?.onSearchEnd) {\r\n let searchEndEvent: SearchEndEvent = {\r\n menuData: filteredResults,\r\n };\r\n this.sidebarData.searchOptions.onSearchEnd(searchEndEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Resets the search input and restores the sidebar menu items.\r\n * Triggers `onSearchStart` and `onSearchEnd` events if provided.\r\n *\r\n * @param {HTMLInputElement} searchInput - The search input element to clear.\r\n */\r\n protected async onCancelSearch(searchInput: HTMLInputElement): Promise<void> {\r\n if (searchInput.value === '') return;\r\n searchInput.value = '';\r\n\r\n let searchStartEvent: SearchStartEvent = {\r\n nativeElement: searchInput,\r\n searchValue: searchInput.value,\r\n cancel: false,\r\n };\r\n\r\n if (this.sidebarData.searchOptions?.onSearchStart) {\r\n await Promise.resolve(\r\n this.sidebarData.searchOptions.onSearchStart(searchStartEvent)\r\n );\r\n }\r\n\r\n if (!searchStartEvent.cancel) {\r\n this.sidebarData = this.deepClone(this.SIDEBAR_DATA);\r\n this.ngSidebarService.sidebarData = this.sidebarData;\r\n\r\n if (this.sidebarData.searchOptions?.onSearchEnd) {\r\n let searchEndEvent: SearchEndEvent = {\r\n menuData: this.SIDEBAR_DATA.sidebarData,\r\n };\r\n this.sidebarData.searchOptions.onSearchEnd(searchEndEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles click events on menu items.\r\n * If the item has children, it toggles the node.\r\n * If the item has a route, it navigates to it.\r\n *\r\n * @param {MenuData} node - The clicked menu item.\r\n * @param {MouseEvent} mouseEvent - The mouse event triggering the click.\r\n */\r\n protected async onMenuClick(node: MenuData, mouseEvent: MouseEvent): Promise<void> {\r\n let event: MenuClickEvent = {\r\n menuData: node,\r\n cancel: false,\r\n };\r\n\r\n await Promise.resolve(node.onClick?.(event));\r\n await Promise.resolve(this.sidebarData.options.onMenuNodeClick?.(event));\r\n\r\n if (event.cancel) return;\r\n\r\n if (node.children && node.children.length > 0) {\r\n this.nodeToggle(node, mouseEvent);\r\n } else if (node.route) {\r\n this.ngSidebarService.router.navigate([node.route]);\r\n }\r\n }\r\n\r\n /**\r\n * Handles click events on favorite menu items.\r\n * Triggers the `onClick` event if it exists for the menu item.\r\n *\r\n * @param {MenuData & { cancel: boolean }} favorite - The favorite menu item clicked.\r\n */\r\n private async onFavoriteClick(\r\n favorite: MenuData & { cancel: boolean }\r\n ): Promise<void> {\r\n let event: MenuClickEvent = {\r\n menuData: favorite,\r\n cancel: false,\r\n };\r\n\r\n await Promise.resolve(favorite.onClick?.(event));\r\n\r\n if (event.cancel) return;\r\n }\r\n\r\n /**\r\n * Expands the sidebar when the mouse enters, if viewMode is set to \"hover\".\r\n */\r\n @HostListener('mouseenter')\r\n protected onEnter(): void {\r\n if (\r\n this.sidebarData.options.viewMode === 'hover' &&\r\n !this.sidebarData.options.pinned\r\n ) {\r\n this.sidebarData.options.expand = true;\r\n }\r\n }\r\n\r\n /**\r\n * Collapses the sidebar when the mouse leaves, if viewMode is set to \"hover\".\r\n */\r\n @HostListener('mouseleave')\r\n protected onLeave(): void {\r\n if (\r\n this.sidebarData.options.viewMode === 'hover' &&\r\n !this.sidebarData.options.pinned\r\n ) {\r\n this.sidebarData.options.expand = false;\r\n }\r\n }\r\n\r\n /**\r\n * Toggles the expansion state of a menu node.\r\n * If the node is already expanded, it collapses it with an animation.\r\n *\r\n * @param {MenuData} node - The menu node to toggle.\r\n * @param {MouseEvent} event - The mouse event triggering the toggle.\r\n */\r\n private async nodeToggle(node: MenuData, event: MouseEvent): Promise<void> {\r\n let nodeTogglerClickEvent: MenuClickEvent = {\r\n menuData: node,\r\n cancel: false,\r\n };\r\n\r\n await Promise.resolve(node.onToggle?.(nodeTogglerClickEvent));\r\n\r\n if (nodeTogglerClickEvent.cancel) return;\r\n\r\n const nodeElement = (event.currentTarget as HTMLElement).querySelector(\r\n '.node-toggler'\r\n );\r\n const parentNode = event.currentTarget as HTMLElement;\r\n\r\n if (node.isExpanded) {\r\n nodeElement!.classList.remove('expand');\r\n Array.from(parentNode.parentElement!.parentElement!.children)\r\n .filter(child => child.classList.contains('node'))\r\n .forEach(child => child.classList.add('out-left'));\r\n setTimeout(() => {\r\n node.isExpanded = false;\r\n }, 300);\r\n } else {\r\n setTimeout(() => {\r\n Array.from(parentNode.parentElement!.parentElement!.children)\r\n .filter(child => child.classList.contains('node'))\r\n .forEach(child => child.classList.add('in-left'));\r\n }, 1);\r\n node.isExpanded = true;\r\n }\r\n }\r\n\r\n /**\r\n * Adds or removes a menu item from the favorites list.\r\n *\r\n * @param {MenuData} node - The menu item to toggle as a favorite.\r\n */\r\n protected onFavoriteNode(node: MenuData): void {\r\n if (!this.favorites.some(fav => fav.name === node.name)) {\r\n this.favorites.push(node);\r\n } else {\r\n this.favorites = this.favorites.filter(fav => fav.name !== node.name);\r\n }\r\n }\r\n\r\n /**\r\n * Checks if a menu item is in the favorites list.\r\n *\r\n * @param {string} name - The name of the menu item to check.\r\n * @returns {boolean} - True if the item is in favorites, false otherwise.\r\n */\r\n protected isOnFav(name: string): boolean {\r\n return this.favorites.some(fav => fav.name === name);\r\n }\r\n\r\n /**\r\n * Updates the favorites list by scanning the sidebar menu items.\r\n */\r\n private updateFavorites(): void {\r\n this.favorites = [];\r\n this.sidebarData.sidebarData.forEach((data: SidebarData) => {\r\n if (data.data) {\r\n this.collectFavorites(data.data);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Recursively collects favorite menu items from a list of nodes.\r\n *\r\n * @param {MenuData[]} nodes - The list of menu nodes to scan.\r\n */\r\n private collectFavorites(nodes: MenuData[]): void {\r\n nodes.forEach(node => {\r\n if (node.isFavorited) {\r\n this.favorites.push(node);\r\n }\r\n if (node.children && node.children.length > 0) {\r\n this.collectFavorites(node.children);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Toggles the pinned state of the sidebar.\r\n * When pinned, the sidebar remains expanded regardless of hover interactions.\r\n */\r\n togglePin(): void {\r\n this.sidebarData.options.pinned = !this.sidebarData.options.pinned;\r\n }\r\n\r\n /**\r\n * Performs a deep clone of an object to prevent reference issues.\r\n *\r\n * @template T\r\n * @param {T} obj - The object to clone.\r\n * @returns {T} - A deep copy of the input object.\r\n */\r\n private deepClone<T>(obj: T): T {\r\n if (obj === null || typeof obj !== 'object') {\r\n return obj;\r\n }\r\n\r\n if (Array.isArray(obj)) {\r\n return obj.map(item => this.deepClone(item)) as unknown as T;\r\n }\r\n\r\n const clonedObj: any = {};\r\n for (const key in obj) {\r\n if (obj.hasOwnProperty(key)) {\r\n clonedObj[key] = this.deepClone(obj[key]);\r\n }\r\n }\r\n return clonedObj;\r\n }\r\n}\r\n","<!-- Sidebar Wrapper -->\r\n<ng-container\r\n *ngIf=\"\r\n sidebarData.options.expand || sidebarData.options.viewMode !== 'mobile'\r\n \">\r\n <!-- Sidebar Resizer -->\r\n <!-- Allows users to resize the sidebar when it is expanded -->\r\n <div\r\n *ngIf=\"sidebarData.options.resize && this.sidebarData.options.expand\"\r\n class=\"resizer\"\r\n (mousedown)=\"ngSidebarService.resize(sidebarData)\"\r\n [ngClass]=\"{ active: ngSidebarService.isResizing }\"></div>\r\n\r\n <!-- Sidebar Banner (Logo & Title) -->\r\n <div class=\"banner\">\r\n <div *ngIf=\"sidebarData.bannerOptions\" class=\"banner-container\">\r\n <!-- Sidebar Logo -->\r\n <al-icon\r\n *ngIf=\"sidebarData.bannerOptions.logo\"\r\n [icon]=\"sidebarData.bannerOptions.logo\"\r\n (click)=\"onBannerClick('logo')\"\r\n class=\"logo\" />\r\n\r\n <!-- Sidebar Title -->\r\n <div\r\n *ngIf=\"sidebarData.bannerOptions.title && sidebarData.options.expand\"\r\n class=\"title text-owerflow\"\r\n (click)=\"onBannerClick('title')\">\r\n {{ sidebarData.bannerOptions.title }}\r\n </div>\r\n </div>\r\n\r\n <!-- Sidebar Toggle Buttons -->\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'toggle'\"\r\n sidebarToggler\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.expand\r\n ? sidebarData.options.toggleCollapseIcon\r\n : sidebarData.options.toggleExpandIcon\r\n \" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'mobile'\"\r\n sidebarToggler\r\n class=\"toggle-icon mobile\"\r\n [icon]=\"sidebarData.options.closeIcon\" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'hover'\"\r\n (click)=\"togglePin()\"\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.pinned\r\n ? sidebarData.options.unpinIcon\r\n : sidebarData.options.pinIcon\r\n \" />\r\n\r\n <ng-content select=\"[al-sidebar-banner]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Top) -->\r\n <ng-content select=\"[al-sidebar-top-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'top'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Search Bar -->\r\n <div\r\n class=\"search\"\r\n *ngIf=\"sidebarData.options.search && sidebarData.options.expand\"\r\n [ngClass]=\"sidebarData.searchOptions?.cssClass\">\r\n <!-- Search Icon -->\r\n <al-icon class=\"search-icon\" icon=\"assets/icons/search.svg\" />\r\n\r\n <!-- Search Input -->\r\n <input\r\n #searchInput\r\n (keyup)=\"onSearch($event)\"\r\n [placeholder]=\"sidebarData.searchOptions?.placeholder ?? ''\"\r\n type=\"text\" />\r\n\r\n <!-- Cancel Search Button -->\r\n <al-icon\r\n *ngIf=\"searchInput.value.length > 0\"\r\n (click)=\"onCancelSearch(searchInput)\"\r\n class=\"cancel-icon\"\r\n icon=\"assets/icons/cancel.svg\"\r\n alt />\r\n\r\n <ng-content select=\"[al-sidebar-search]\"></ng-content>\r\n </div>\r\n\r\n <!-- Sidebar Menu Items -->\r\n <div class=\"menu-container\">\r\n <!-- Favorites Section -->\r\n <div\r\n *ngIf=\"favorites.length > 0 && sidebarData.options.expand\"\r\n class=\"wrapper favorites\">\r\n <div class=\"title\">{{ sidebarData.options.favoritesTitle }}</div>\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: favorites }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Sidebar Sections & Items -->\r\n <div\r\n [class]=\"'wrapper ' + data.cssClass\"\r\n [ngClass]=\"{\r\n collapsed:\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile',\r\n }\"\r\n *ngFor=\"let data of sidebarData.sidebarData\">\r\n <ng-container *ngIf=\"data.visible\">\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"title\">\r\n {{ data.title }}\r\n </div>\r\n\r\n <!-- Expanded Menu Items -->\r\n <ng-container *ngIf=\"sidebarData.options.expand\">\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: data.data }\">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Collapsed Menu Items -->\r\n <ng-container\r\n *ngIf=\"\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile'\r\n \">\r\n ...\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: data.data }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n <ng-content select=\"[al-sidebar-menu]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Bottom) -->\r\n <ng-content select=\"[al-sidebar-bottom-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'bottom'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Templates for Menu Items & User Profile -->\r\n <ng-template #dataTemplate let-nodes>\r\n <div class=\"node\" *ngFor=\"let node of nodes\">\r\n <div class=\"node-wrapper\">\r\n <div\r\n class=\"node-info\"\r\n (click)=\"onMenuClick(node, $event)\"\r\n [ngClass]=\"{ 'no-child': !node.children, active: node.active }\">\r\n <div class=\"node-title\">\r\n <div class=\"icon-container\">\r\n <al-icon [icon]=\"node.icon ?? 'assets/icons/circle-dot.svg'\" />\r\n </div>\r\n {{ node.name }}\r\n </div>\r\n <div class=\"node-icons\">\r\n <div *ngIf=\"node.children\">\r\n <al-icon\r\n class=\"node-toggler transition\"\r\n [ngClass]=\"{ expand: node.isExpanded }\"\r\n icon=\"assets/icons/node-toggle.svg\"></al-icon>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Favorite Icon -->\r\n <div class=\"favorite-container\" (click)=\"onFavoriteNode(node)\">\r\n <al-icon\r\n *ngIf=\"\r\n sidebarData.options.favorites &&\r\n (!node.children || (node.children && node.children?.length == 0))\r\n \"\r\n class=\"node-favorite\"\r\n [icon]=\"\r\n isOnFav(node.name)\r\n ? 'assets/icons/favorite.svg'\r\n : 'assets/icons/favorite-outline.svg'\r\n \"\r\n alt=\"Favorite\">\r\n </al-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Expandable Child Nodes -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n dataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- User Profile Template -->\r\n <ng-template #userTemplate let-user>\r\n <div\r\n class=\"user-container\"\r\n [ngClass]=\"{\r\n open: sidebarData.options.expand,\r\n close: !sidebarData.options.expand,\r\n }\">\r\n <div class=\"user\" [ngClass]=\"user.cssClass\">\r\n <al-icon [icon]=\"user.avatar\" />\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"name\">\r\n {{ user.name }}\r\n </div>\r\n </div>\r\n <div class=\"theme-toggler\">\r\n <al-theme-toggler></al-theme-toggler>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Collapsed Sidebar Template -->\r\n <ng-template #collapseDataTemplate let-nodes>\r\n <ng-container *ngFor=\"let node of nodes\">\r\n <!-- Collapsed Menu Item Icon -->\r\n <al-icon\r\n *ngIf=\"node.icon\"\r\n class=\"collapsed-node-icon\"\r\n [icon]=\"node.icon\"></al-icon>\r\n\r\n <!-- Recursively Render Child Nodes in Collapsed State -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </ng-template>\r\n</ng-container>\r\n","import { CommonModule, NgStyle } from '@angular/common';\r\nimport { NgModule } from '@angular/core';\r\nimport { NgSidebarComponent } from './component/ng-sidebar.component';\r\nimport { AlIconComponent } from './component/al-icon/al-icon.component';\r\nimport { HttpClientModule } from '@angular/common/http';\r\nimport { TogglerDirective } from './toggler.directive';\r\nimport { ThemeTogglerComponent } from './component/theme-toggler/theme-toggler.component';\r\n\r\n@NgModule({\r\n declarations: [\r\n NgSidebarComponent,\r\n AlIconComponent,\r\n TogglerDirective,\r\n ThemeTogglerComponent,\r\n ],\r\n imports: [CommonModule, NgStyle, HttpClientModule],\r\n exports: [NgSidebarComponent, TogglerDirective, ThemeTogglerComponent],\r\n})\r\nexport class NgSidebarModule {}\r\n","/*\r\n * Public API Surface of ng-sidebar\r\n */\r\n\r\nexport * from './lib/ng-sidebar.service';\r\nexport * from './lib/component/ng-sidebar.component';\r\nexport * from './lib/sidebar.model';\r\nexport * from './lib/component/theme-toggler/theme-toggler.component';\r\nexport * from './lib/component/al-icon/al-icon.component';\r\nexport * from './lib/ng-sidebar.module';\r\nexport * from './lib/toggler.directive';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1","i2.NgSidebarService","i1.NgSidebarService","i2","i3.AlIconComponent","i4.TogglerDirective","i5.ThemeTogglerComponent"],"mappings":";;;;;;;;;;AAWA;;;;;;AAMG;MAIU,gBAAgB,CAAA;AAuB3B;;;;;AAKG;IACH,WACS,CAAA,MAAc,EACb,IAAgB,EAAA;QADjB,IAAM,CAAA,MAAA,GAAN,MAAM,CAAQ;QACb,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAY;AA9B1B;;;AAGG;QACH,IAAkB,CAAA,kBAAA,GAAY,KAAK,CAAC;AAEpC;;;AAGG;QACH,IAAU,CAAA,UAAA,GAAY,KAAK,CAAC;AAsB1B,QAAA,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,IAAG;AAC9B,YAAA,IAAI,KAAK,YAAY,aAAa,EAAE;gBAClC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,IAAG;oBAC1C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;AAC/C,iBAAC,CAAC,CAAC;aACJ;AACH,SAAC,CAAC,CAAC;KACJ;AAED;;;;;AAKG;AACH,IAAA,oBAAoB,CAClB,IAA4D,EAAA;;AAG5D,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa;AACrC,cAAE;AACE,gBAAA,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,+BAA+B;AAChE,gBAAA,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,KAAK,IAAI,WAAW;AAC9C,gBAAA,OAAO,EAAE