{"version":3,"file":"ngwr-sidebar.mjs","sources":["../../../projects/lib/sidebar/sidebar.ts","../../../projects/lib/sidebar/sidebar.html","../../../projects/lib/sidebar/ngwr-sidebar.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, effect, inject, input, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSidebarEntry, WrSidebarGroup, WrSidebarItem } from './interfaces';\n\nfunction isGroup(entry: WrSidebarEntry): entry is WrSidebarGroup {\n  return 'children' in entry;\n}\n\n/**\n * Data-driven sidebar nav. Pass `[entries]` — either flat\n * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal\n * child items). Active route auto-expands its containing group.\n *\n * @example\n * ```html\n * <wr-sidebar [entries]=\"[\n *   { title: 'Dashboard', icon: 'home', url: ['/dashboard'] },\n *   {\n *     title: 'Settings',\n *     icon: 'cog',\n *     children: [\n *       { title: 'Profile', url: ['/settings', 'profile'] },\n *       { title: 'Billing', url: ['/settings', 'billing'] }\n *     ]\n *   }\n * ]\" />\n * ```\n *\n * @see https://ngwr.dev/components/sidebar\n */\n@Component({\n  selector: 'wr-sidebar',\n  templateUrl: './sidebar.html',\n  encapsulation: ViewEncapsulation.None,\n  host: { class: 'wr-sidebar', role: 'navigation' },\n  imports: [RouterLink, RouterLinkActive, WrIcon],\n})\nexport class WrSidebar {\n  readonly entries = input<readonly WrSidebarEntry[]>([]);\n\n  /** Default icon for entries without one. @default 'folder' */\n  readonly defaultGroupIcon = input<string>('folder');\n\n  /** Default icon for items without one. @default 'caret-forward' */\n  readonly defaultItemIcon = input<string>('caret-forward');\n\n  /** Auto-expand the group containing the active route. @default true */\n  readonly autoExpand = input(true, { transform: coerceBooleanProperty });\n\n  protected readonly isGroup = isGroup;\n\n  private readonly opened = signal<ReadonlySet<string>>(new Set());\n\n  private readonly router = inject(Router);\n  private readonly currentUrl = toSignal(\n    this.router.events.pipe(\n      filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n      map(e => e.urlAfterRedirects),\n      startWith(this.router.url)\n    ),\n    { initialValue: this.router.url }\n  );\n\n  /** Last URL we auto-expanded — prevents re-opening a group the user collapsed. */\n  private lastAutoUrl: string | null = null;\n\n  constructor() {\n    // Seed defaultOpen groups.\n    effect(() => {\n      const seeded = new Set<string>();\n      for (const entry of this.entries()) {\n        if (isGroup(entry) && entry.defaultOpen) seeded.add(entry.title);\n      }\n      if (seeded.size > 0) {\n        this.opened.update(prev => new Set([...prev, ...seeded]));\n      }\n    });\n\n    // Auto-expand the group containing the current URL.\n    effect(() => {\n      if (!this.autoExpand()) return;\n      const url = this.currentUrl();\n      if (url === this.lastAutoUrl) return;\n      this.lastAutoUrl = url;\n      const match = this.findGroupForUrl(url);\n      if (!match || this.opened().has(match)) return;\n      this.opened.update(prev => {\n        const next = new Set(prev);\n        next.add(match);\n        return next;\n      });\n    });\n  }\n\n  protected isOpen(title: string): boolean {\n    return this.opened().has(title);\n  }\n\n  /** Stable id for the group body, referenced by the toggle's `aria-controls`. */\n  protected groupBodyId(title: string): string {\n    return `wr-sidebar-group-${title.replace(/\\s+/g, '-').toLowerCase()}`;\n  }\n\n  protected toggleGroup(title: string): void {\n    this.opened.update(prev => {\n      const next = new Set(prev);\n      if (next.has(title)) next.delete(title);\n      else next.add(title);\n      return next;\n    });\n  }\n\n  protected asItem(entry: WrSidebarEntry): WrSidebarItem {\n    return entry as WrSidebarItem;\n  }\n\n  protected asGroup(entry: WrSidebarEntry): WrSidebarGroup {\n    return entry as WrSidebarGroup;\n  }\n\n  private findGroupForUrl(url: string): string | null {\n    for (const entry of this.entries()) {\n      if (!isGroup(entry)) continue;\n      const hit = entry.children.some(child => url.startsWith(`/${child.url.join('/')}`));\n      if (hit) return entry.title;\n    }\n    return null;\n  }\n}\n","<nav class=\"wr-sidebar__inner\">\n  @for (entry of entries(); track entry.title) {\n    @if (!isGroup(entry)) {\n      <a\n        class=\"wr-sidebar__entry\"\n        routerLinkActive=\"wr-sidebar__entry--active\"\n        [routerLink]=\"asItem(entry).url\"\n        [class.wr-sidebar__entry--disabled]=\"asItem(entry).disabled\"\n      >\n        @if (asItem(entry).icon || defaultItemIcon(); as name) {\n          <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n        }\n        <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n        @if (asItem(entry).badge) {\n          <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n        }\n      </a>\n    } @else {\n      <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n        <button\n          type=\"button\"\n          class=\"wr-sidebar__group-toggle\"\n          [attr.aria-expanded]=\"isOpen(entry.title)\"\n          [attr.aria-controls]=\"groupBodyId(entry.title)\"\n          (click)=\"toggleGroup(entry.title)\"\n        >\n          @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n            <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n          }\n          <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n          <!-- Inline chrome chevron — not user-customizable. -->\n          <svg\n            class=\"wr-icon__svg wr-sidebar__chevron\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"m6 9 6 6 6-6\" />\n          </svg>\n        </button>\n        <div\n          class=\"wr-sidebar__group-wrap\"\n          [attr.id]=\"groupBodyId(entry.title)\"\n          [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n        >\n          <div class=\"wr-sidebar__group-body\">\n            <ul class=\"wr-sidebar__list\">\n              @for (child of asGroup(entry).children; track child.title) {\n                <li>\n                  @if (child.disabled || !child.url.length) {\n                    <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n                      @if (child.icon || defaultItemIcon(); as name) {\n                        <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n                      }\n                      <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n                      @if (child.badge) {\n                        <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n                      }\n                    </span>\n                  } @else {\n                    <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n                      @if (child.icon || defaultItemIcon(); as name) {\n                        <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n                      }\n                      <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n                      @if (child.badge) {\n                        <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n                      }\n                    </a>\n                  }\n                </li>\n              }\n            </ul>\n          </div>\n        </div>\n      </section>\n    }\n  }\n</nav>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}