@lifecorner/angular-ui-kit 3.34.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +1 -0
  2. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
  3. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
  4. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-button.mjs +6 -6
  6. package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-card.mjs +82 -16
  8. package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +23 -4
  10. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +43 -5
  12. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs +30 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
  16. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +45 -8
  17. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  18. package/fesm2022/lifecorner-angular-ui-kit-hero.mjs +57 -5
  19. package/fesm2022/lifecorner-angular-ui-kit-hero.mjs.map +1 -1
  20. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs +6 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs.map +1 -1
  22. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +24 -5
  23. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  24. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs +2 -4
  25. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs.map +1 -1
  26. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +41 -1
  27. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  28. package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs +47 -5
  29. package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs.map +1 -1
  30. package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs +203 -0
  31. package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs.map +1 -0
  32. package/fesm2022/lifecorner-angular-ui-kit-table.mjs +25 -4
  33. package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
  34. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +3 -0
  35. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  36. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
  37. package/fesm2022/lifecorner-angular-ui-kit.mjs +1 -0
  38. package/package.json +5 -1
  39. package/src/styles/theme-contrast.spec.ts +74 -0
  40. package/types/lifecorner-angular-ui-kit-alert.d.ts +12 -2
  41. package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +5 -1
  42. package/types/lifecorner-angular-ui-kit-button.d.ts +7 -3
  43. package/types/lifecorner-angular-ui-kit-card.d.ts +51 -3
  44. package/types/lifecorner-angular-ui-kit-combobox.d.ts +36 -0
  45. package/types/lifecorner-angular-ui-kit-description-list.d.ts +40 -0
  46. package/types/lifecorner-angular-ui-kit-drawer.d.ts +37 -1
  47. package/types/lifecorner-angular-ui-kit-gallery.d.ts +3 -1
  48. package/types/lifecorner-angular-ui-kit-header.d.ts +44 -2
  49. package/types/lifecorner-angular-ui-kit-hero.d.ts +32 -0
  50. package/types/lifecorner-angular-ui-kit-internal-overlay.d.ts +9 -1
  51. package/types/lifecorner-angular-ui-kit-list.d.ts +17 -0
  52. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +42 -1
  53. package/types/lifecorner-angular-ui-kit-stacked-bar-chart.d.ts +36 -0
  54. package/types/lifecorner-angular-ui-kit-state-strip.d.ts +107 -0
  55. package/types/lifecorner-angular-ui-kit-table.d.ts +37 -0
  56. package/types/lifecorner-angular-ui-kit-textarea.d.ts +17 -0
  57. package/types/lifecorner-angular-ui-kit-toast.d.ts +4 -2
  58. package/types/lifecorner-angular-ui-kit.d.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-sidenav.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/sidenav/sidenav.component.ts","../../../../libs/angular-ui-kit/src/lib/sidenav/sidenav.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n signal,\n computed,\n input,\n output,\n model,\n effect,\n untracked,\n HostListener,\n OnDestroy,\n inject,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { NgTemplateOutlet, NgStyle } from '@angular/common';\nimport { NavigationEnd, Router, RouterLink } from '@angular/router';\nimport { filter } from 'rxjs';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { NavigationItem } from '@lifecorner/angular-ui-kit/models';\nimport {\n currentItemParents,\n currentNavigationItem,\n navigationLinks,\n navigationPath,\n sortNavigationItems,\n} from '@lifecorner/angular-ui-kit/internal/navigation';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT } from '@lifecorner/angular-ui-kit/icons';\nimport { BadgeComponent } from '@lifecorner/angular-ui-kit/badge';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\n\nexport type SidenavPosition = 'left' | 'right';\nexport type SidenavMode = 'drawer' | 'docked';\n\n/**\n * Surface of the docked sidenav: `default` is the rail on `--color-sidebar`\n * with a border; `transparent` has no background and no border and draws the\n * items as 48 px pills (the side navigation on the page, \"Weite\"). A drawer\n * keeps its surface in both.\n */\nexport type SidenavVariant = 'default' | 'transparent';\n\n/**\n * Sidenav component for application navigation sidebar.\n *\n * Features:\n * - Drawer (overlay) and docked (persistent) modes\n * - Responsive mobile mode — docked automatically switches to drawer below a configurable breakpoint\n * - Collapsed icon-rail mode (narrow 56px sidebar with icons only and hover tooltips)\n * - Integrated logo area with collapse-toggle (sidebar-first layouts)\n * - Text brand (`brandName`, `brandIcon`): an icon tile and the product name,\n * one link to home (`brandHref`, else `routerLink=\"/\"`), as high as the\n * `lc-header` next to it; the rail shows only the tile. It never toggles:\n * `showCollapseToggle` adds a collapse button at the bottom of the rail\n * - Hierarchical navigation with collapsible groups (up to 3 levels)\n * - Section headlines for item grouping with optional action buttons\n * - Action buttons on any item (hover-reveal)\n * - Badge support on items (count / status indicator), read after the label\n * through `badgeAriaLabel` (3.31); on the collapsed rail the label and the\n * badge text name the item\n * - One current item, announced and painted alike: the link for `activeRoute`\n * (or, without it, the one the router's URL matches) gets\n * `aria-current=\"page\"` and the current look, every other link neither\n * (AS-16). No hover color change on the current item\n * - Keyboard navigation support (Escape closes drawer)\n * - Configurable width and position (left/right)\n * - Optional backdrop overlay\n * - Theme variants: auto, light, dark (with teal accent for dark mode). The\n * theming hooks (`--lc-sidenav-bg`, `-fg`, `-fg-active`, `-border`,\n * `-hover-bg`, `-section-fg`, `-active-bg`, `-active-fg`, `-active-icon`,\n * `-badge-ring`) can be set on `lc-sidenav` or any ancestor (3.15); a value\n * on the element wins over an ancestor's, and both win over `theme` (3.14).\n * Set the same ones for `lc-sidenav-placeholder`, e.g. on a shared ancestor;\n * `--lc-sidenav-active-indicator`\n * (the current item's 2 px bar, default `--color-primary-500`; on a\n * `theme=\"light\"` rail #208497 on any page, like its focus rings),\n * `--lc-sidenav-brand-tile-bg` (default `--color-primary-fill`) and\n * `--lc-sidenav-brand-tile-fg` (default `--color-on-primary`) can be set on\n * any ancestor\n * - `variant=\"transparent\"`: no rail background or border, items as 48 px\n * pills with 16 px labels, the current one on `--color-surface-selected`\n * - Items are at least 44 px high in every density; the current item's ink is\n * `--color-on-surface-selected` (AA on the selected tint)\n * - Accessible: the inner `<nav>` is the navigation landmark; in drawer mode\n * the panel is a modal dialog (`role=\"dialog\"`, focus moved to the close\n * button on open, trapped while open, restored on close)\n *\n * @example\n * ```html\n * <lc-sidenav\n * [isOpen]=\"isOpen\"\n * mode=\"docked\"\n * [(collapsed)]=\"isCollapsed\"\n * [showLogo]=\"true\"\n * logoSize=\"md\"\n * [mobileBreakpoint]=\"768\"\n * [items]=\"navItems\"\n * activeRoute=\"/dashboard\"\n * theme=\"dark\"\n * (closed)=\"isOpen = false\"\n * (itemClicked)=\"navigate($event)\"\n * (itemAction)=\"onAction($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-sidenav',\n standalone: true,\n imports: [NgTemplateOutlet, NgStyle, RouterLink, A11yModule, IconComponent, BadgeComponent, LogoComponent],\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT])],\n templateUrl: './sidenav.component.html',\n styleUrl: './sidenav.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.lc-sidenav-container]': 'true',\n '[class.lc-sidenav-container--docked]': \"effectiveMode() === 'docked'\",\n '[class.lc-sidenav-container--open]': 'isOpen()',\n // `theme` on the host (019): the themed values are private host variables\n // that the public `--lc-sidenav-*` hooks fall back to, so an app's hooks\n // on `lc-sidenav` win over `theme=\"dark\"` / `\"light\"`.\n '[class.lc-sidenav-container--light]': \"theme() === 'light'\",\n '[class.lc-sidenav-container--dark]': \"theme() === 'dark'\",\n },\n})\nexport class SidenavComponent implements OnDestroy {\n private readonly router = inject(Router);\n\n /**\n * The router's URL, updated on every `NavigationEnd`. Before the first\n * navigation it is the router's initial URL (`/`). Read only when\n * `activeRoute` is empty.\n */\n private readonly routerUrl = signal(this.router.url);\n\n /** Whether the sidenav is collapsed to icon-only rail (two-way bindable) */\n readonly collapsed = model(false);\n\n /** Whether to show the logo at the top of the sidenav */\n readonly showLogo = input(false);\n\n /** Custom URL for the full brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoSrc = input('');\n /** Custom URL for the emblem-only brand logo. */\n readonly logoEmblemSrc = input('');\n /** Optional dark-theme URL for the full brand logo. */\n readonly logoDarkSrc = input('');\n /** Optional dark-theme URL for the emblem-only brand logo. */\n readonly logoDarkEmblemSrc = input('');\n /** Alt text for the brand logo. */\n readonly logoAlt = input('');\n\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md` for a prominent brand block aligned with `<lc-header size=\"md\">` (both 64px). Also sets the height of the text brand (`brandName`). */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n\n /**\n * Product name shown as a text brand at the top: an icon tile and the name,\n * one link to home. Replaces the image logo (`showLogo`). The block is as\n * high as the image logo (`logoSize`: 64 px for `md`, matching\n * `lc-header size=\"md\"`); the name wraps to two lines at most, and the full\n * name stays the link's accessible name. Collapsed, only the tile shows.\n * The brand never toggles the rail (see `showCollapseToggle`). Under\n * `@defer`, give the `lc-sidenav-placeholder` `[showLogo]=\"true\"` and the\n * same `logoSize` to reserve the block. Empty: no text brand.\n * @default ''\n */\n readonly brandName = input('');\n\n /**\n * `lc-icon` name in the brand tile (decorative). Empty: the tile shows the\n * name's first letter. Renders nothing without `brandName`.\n * @default ''\n */\n readonly brandIcon = input('');\n\n /**\n * Plain URL of the brand link (a native `<a href>`, works without the\n * router). Empty: `routerLink=\"/\"`, as `lc-header`'s `homeHref`.\n * @default ''\n */\n readonly brandHref = input('');\n\n /**\n * Accessible name of the brand link. Empty: the brand name. Start it with\n * the visible name, e.g. `'Finance Intelligence – home'`.\n * @default ''\n */\n readonly brandAriaLabel = input('');\n\n /**\n * Docked mode: a button at the bottom of the rail collapses and expands it\n * (`collapsed`). Independent of the brand. With `showLogo` the image logo\n * toggles already, so use one of the two.\n * @default false\n */\n readonly showCollapseToggle = input(false);\n\n /**\n * Accessible name of the collapse button (`showCollapseToggle`) while the\n * rail is collapsed. Expanded, it uses `collapseAriaLabel`.\n * @default 'Expand navigation'\n */\n readonly expandAriaLabel = input('Expand navigation');\n\n /** Whether the sidenav is open */\n readonly isOpen = input(false);\n\n /** Display mode: 'drawer' (overlay) or 'docked' (persistent sidebar) */\n readonly mode = input<SidenavMode>('drawer');\n\n /** Mobile breakpoint in pixels. Below this width, docked mode switches to drawer. */\n readonly mobileBreakpoint = input(768);\n\n /** Whether the viewport is below the mobile breakpoint */\n readonly isMobile = signal(false);\n\n /** Effective mode: switches docked → drawer on mobile */\n readonly effectiveMode = computed(() => {\n if (this.isMobile() && this.mode() === 'docked') {\n return 'drawer' as SidenavMode;\n }\n return this.mode();\n });\n\n private mediaQuery: MediaQueryList | null = null;\n private mediaHandler = (e: MediaQueryListEvent) => this.isMobile.set(e.matches);\n\n /** Position of the sidenav (left or right) */\n readonly position = input<SidenavPosition>('left');\n\n /** Width of the sidenav drawer */\n readonly width = input('320px');\n\n /**\n * ARIA label of the navigation landmark (the inner `<nav>`) and, in drawer\n * mode, of the dialog panel.\n */\n readonly ariaLabel = input('Side navigation');\n\n /** Whether to show the overlay backdrop */\n readonly hasOverlay = input(true);\n\n /**\n * Navigation items (`NavigationItem[]`): `id`, `icon`, `label`, `route`,\n * `displayOrder` (sort order, 1 = first); optional `children`\n * (`NavigationItem[]`, a collapsible group, up to 3 levels), `isSection`\n * (renders `label` as a section headline over its `children`), `badge`\n * (`{ value: string | number; variant?: 'default' | 'primary' | 'success' |\n * 'warning' | 'error' | 'info' }`), `action` (`{ icon: string; ariaLabel?:\n * string }`, a button that emits `itemAction`), `requiredRole` (for the\n * app's own filtering; not evaluated here). Items link with `routerLink`;\n * exactly one link, the current item (see `activeRoute`), gets\n * `aria-current=\"page\"`.\n * @default []\n */\n readonly items = input<NavigationItem[]>([]);\n\n /**\n * The current route. When set, the first link (in display order) whose\n * `route` equals it is the current item: it alone gets `aria-current=\"page\"`\n * and the current look, whatever the router matches. When empty, the\n * sidenav follows the router: the link whose route equals the URL's path is\n * current, otherwise the one with the longest route the path starts with\n * (`/household` on `/household/members`; `/` only on `/`). Routes are\n * compared as written, so give them as absolute paths (`'/household'`).\n * @default ''\n */\n readonly activeRoute = input('');\n\n /** Theme variant for the sidenav */\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n\n /**\n * Surface of the docked sidenav: `default` (the rail with background and\n * border) or `transparent` (no background, no border, items as 48 px pills\n * with radius full and 16 px labels; the current one on\n * `--color-surface-selected`). A drawer keeps its surface.\n */\n readonly variant = input<SidenavVariant>('default');\n\n /** Accessible name of the logo button that collapses/expands the sidebar. */\n readonly toggleAriaLabel = input('Toggle sidebar');\n\n /** Accessible name of the collapse button. Empty: \"Collapse navigation to the left/right\" by `position`. */\n readonly collapseAriaLabel = input('');\n\n /** Tooltip of the collapse button. */\n readonly collapseTitle = input('Collapse sidebar');\n\n /** Accessible name of the close button (drawer mode). */\n readonly closeAriaLabel = input('Close navigation');\n\n /**\n * Text read after an item's label when it has a badge, at every level. On\n * the collapsed rail, where labels are hidden, the label and this text name\n * the item. `{value}` is replaced with the badge value, e.g.\n * `'{value} offen'` → \"Stammdaten 1 offen\". The badge itself is hidden from\n * screen readers. Empty: the badge isn't read. Since 3.33 a badged rail\n * item has no `title` (it repeated the label as a description); its\n * tooltip still shows on hover and focus.\n *\n * @default '{value}'\n */\n readonly badgeAriaLabel = input('{value}');\n\n /**\n * Ids of the expanded parent items — the single source of truth for\n * `isExpanded`. Seeded with the current item's parent whenever it changes\n * (see constructor), so a group opens on navigation but can still\n * be collapsed by the user. (It used to be OR-ed with `hasActiveChild` at\n * read time, so toggling such a group flipped the set without ever closing\n * it — the set drifted out of sync with what was on screen.)\n */\n readonly expandedItems = signal<Set<string>>(new Set());\n\n /** Ids of the collapsible parents (top-level groups, section sub-groups) that contain the current item. */\n private readonly activeChildParents = computed(() => currentItemParents(this.items(), this.currentItem()));\n\n /**\n * Computed sorted navigation items by displayOrder\n */\n readonly sortedItems = computed(() => sortNavigationItems(this.items()));\n\n /**\n * Every item rendered as a link, in display order (depth first): simple\n * items, the children of a group, the children of a section, and the\n * children of a section's sub-group.\n */\n private readonly linkItems = computed(() => navigationLinks(this.sortedItems()));\n\n /** The router URL's path: primary outlet only, no query, fragment or matrix parameters. */\n private readonly routerPath = computed(() => navigationPath(this.router, this.routerUrl()));\n\n /**\n * The one current link (or none). The single source of both the current\n * look (`lc-sidenav__nav-item--active`) and `aria-current=\"page\"` (AS-16).\n * With `activeRoute` set: the first link whose route equals it. Without:\n * the router's path — the exact match wins, otherwise the longest link\n * route the path continues with a `/` (`/` matches only `/`); ties go to\n * the first link in display order. The rules live in\n * `internal/navigation` since 3.6, shared with `lc-sidenav-placeholder`.\n */\n private readonly currentItem = computed<NavigationItem | null>(() =>\n currentNavigationItem(this.linkItems(), this.activeRoute(), this.activeRoute() === '' ? this.routerPath() : ''),\n );\n\n /**\n * Event emitted when the sidenav should close\n */\n readonly closed = output<void>();\n\n /**\n * Event emitted when a navigation item is clicked\n */\n readonly itemClicked = output<NavigationItem>();\n\n /**\n * Event emitted when an item's action button is clicked\n */\n readonly itemAction = output<NavigationItem>();\n\n constructor() {\n this.router.events\n .pipe(\n filter((event): event is NavigationEnd => event instanceof NavigationEnd),\n takeUntilDestroyed(),\n )\n .subscribe(() => this.routerUrl.set(this.router.url));\n\n // React to mobileBreakpoint changes\n effect(() => {\n this.mobileBreakpoint(); // track dependency\n this.setupMediaQuery();\n });\n\n // Navigating into a group opens it: add the active child's parent(s) to\n // the expanded set. Only additive — the user's own collapses/expands of\n // other groups survive a route change.\n effect(() => {\n const parents = this.activeChildParents();\n if (parents.length === 0) return;\n const expanded = untracked(this.expandedItems);\n if (parents.every((id) => expanded.has(id))) return;\n this.expandedItems.set(new Set([...expanded, ...parents]));\n });\n }\n\n ngOnDestroy(): void {\n this.teardownMediaQuery();\n }\n\n private setupMediaQuery(): void {\n if (typeof window === 'undefined') return;\n this.teardownMediaQuery();\n this.mediaQuery = window.matchMedia(`(max-width: ${this.mobileBreakpoint()}px)`);\n this.isMobile.set(this.mediaQuery.matches);\n this.mediaQuery.addEventListener('change', this.mediaHandler);\n }\n\n private teardownMediaQuery(): void {\n this.mediaQuery?.removeEventListener('change', this.mediaHandler);\n this.mediaQuery = null;\n }\n\n /**\n * Computed CSS classes for the sidenav\n */\n sidenavClasses = computed(() => {\n const classes = ['lc-sidenav'];\n classes.push(`lc-sidenav--${this.position()}`);\n classes.push(`lc-sidenav--${this.effectiveMode()}`);\n if (this.isOpen()) {\n classes.push('lc-sidenav--open');\n }\n if (this.collapsed() && !this.isMobile()) {\n classes.push('lc-sidenav--collapsed');\n }\n if (this.theme() !== 'auto') {\n classes.push(`lc-sidenav--${this.theme()}`);\n }\n if (this.variant() === 'transparent') {\n classes.push('lc-sidenav--transparent');\n }\n return classes.join(' ');\n });\n\n /**\n * Computed inline styles for the sidenav\n */\n sidenavStyles = computed(() => ({\n width: this.isMobile() ? this.width() : (this.collapsed() ? '56px' : this.width()),\n }));\n\n /** The brand tile's letter when there is no `brandIcon`: the name's first character, upper-cased. */\n protected readonly brandInitial = computed(() => ([...this.brandName().trim()][0] ?? '').toUpperCase());\n\n /** Whether the rail shows only icons (as `lc-sidenav--collapsed`). */\n protected readonly isRail = computed(() => this.collapsed() && !this.isMobile());\n\n /** Visually hidden badge text: `badgeAriaLabel` with `{value}`; on the rail, where labels are hidden, after the label. */\n protected badgeText(item: NavigationItem): string {\n if (!item.badge) return '';\n const text = this.badgeAriaLabel().replace('{value}', String(item.badge.value));\n return this.isRail() ? `${item.label} ${text}` : text;\n }\n\n /** The collapse button's accessible name: what a press does. */\n protected readonly collapseToggleLabel = computed(() =>\n this.collapsed()\n ? this.expandAriaLabel()\n : this.collapseAriaLabel() ||\n (this.position() === 'right' ? 'Collapse navigation to the right' : 'Collapse navigation to the left'),\n );\n\n /** The collapse button's chevron points where the rail moves: ⟨ to collapse a left rail, ⟩ to expand it (mirrored on the right). */\n protected readonly collapseToggleIcon = computed(() => {\n const towardsLeft = this.position() === 'right' ? this.collapsed() : !this.collapsed();\n return towardsLeft ? 'chevron-left' : 'chevron-right';\n });\n\n /**\n * Toggle collapsed state\n */\n toggleCollapsed(): void {\n this.collapsed.set(!this.collapsed());\n }\n\n /** The brand link closes the drawer on a phone, as the items do. */\n handleBrandClick(): void {\n if (this.isMobile()) {\n this.handleClose();\n }\n }\n\n /**\n * Handle close action\n */\n handleClose(): void {\n this.closed.emit();\n }\n\n /**\n * Handle navigation item click\n */\n handleItemClick(item: NavigationItem): void {\n this.itemClicked.emit(item);\n // Auto-close drawer on mobile after navigation\n if (this.isMobile()) {\n this.handleClose();\n }\n }\n\n /**\n * Handle action button click on a navigation item\n */\n handleItemAction(event: Event, item: NavigationItem): void {\n event.stopPropagation();\n this.itemAction.emit(item);\n }\n\n /**\n * Whether the item is the current link (see `currentItem`): it alone is\n * painted current and carries `aria-current=\"page\"`.\n */\n isItemActive(item: NavigationItem): boolean {\n return this.currentItem() === item;\n }\n\n /**\n * Check if a parent item contains the current link\n */\n hasActiveChild(item: NavigationItem): boolean {\n const current = this.currentItem();\n return !!current && !!item.children?.includes(current);\n }\n\n /**\n * Toggle expansion of a parent item.\n * If the sidenav is collapsed, expand it first.\n */\n toggleExpanded(item: NavigationItem): void {\n if (this.collapsed()) {\n this.collapsed.set(false);\n // Expand the clicked item after uncollapsing\n const expanded = new Set(this.expandedItems());\n expanded.add(item.id);\n this.expandedItems.set(expanded);\n return;\n }\n const expanded = new Set(this.expandedItems());\n if (expanded.has(item.id)) {\n expanded.delete(item.id);\n } else {\n expanded.add(item.id);\n }\n this.expandedItems.set(expanded);\n }\n\n /**\n * Check if an item is expanded\n */\n isExpanded(item: NavigationItem): boolean {\n return this.expandedItems().has(item.id);\n }\n\n /**\n * Handle keyboard navigation. Keyed on the EFFECTIVE mode: a docked sidenav\n * that has become a drawer below the mobile breakpoint must close on Escape\n * like any drawer.\n */\n @HostListener('document:keydown', ['$event'])\n handleKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.isOpen() && this.effectiveMode() === 'drawer') {\n this.handleClose();\n }\n }\n}\n","<!-- Drawer mode: render with overlay and conditional visibility -->\n@if (effectiveMode() === 'drawer') {\n @if (isOpen()) {\n <!-- Overlay backdrop -->\n @if (hasOverlay()) {\n <div\n class=\"lc-sidenav__overlay\"\n (click)=\"handleClose()\"\n aria-hidden=\"true\">\n </div>\n }\n\n <ng-container *ngTemplateOutlet=\"sidenavPanel\" />\n }\n}\n\n<!-- Docked mode: always render, visibility controlled by CSS -->\n@if (effectiveMode() === 'docked') {\n <ng-container *ngTemplateOutlet=\"sidenavPanel\" />\n}\n\n<!-- Shared sidenav panel template.\n The <aside> is a plain complementary region; the navigation landmark is\n the inner <nav> (nesting a role=\"navigation\" wrapper around a <nav> gave\n two navigation landmarks). In drawer mode the panel is a modal dialog:\n focus is captured (close button first, see cdkFocusInitial), trapped\n while open and restored to the opener when the panel is destroyed. -->\n<ng-template #sidenavPanel>\n <aside\n [class]=\"sidenavClasses()\"\n [ngStyle]=\"sidenavStyles()\"\n [attr.role]=\"effectiveMode() === 'drawer' ? 'dialog' : null\"\n [attr.aria-modal]=\"effectiveMode() === 'drawer' ? 'true' : null\"\n [attr.aria-label]=\"effectiveMode() === 'drawer' ? ariaLabel() : null\"\n [cdkTrapFocus]=\"effectiveMode() === 'drawer'\"\n [cdkTrapFocusAutoCapture]=\"effectiveMode() === 'drawer'\">\n\n <!-- Text brand (019): one link to home, never a toggle. Replaces the image logo. -->\n @if (brandName()) {\n <div\n class=\"lc-sidenav__logo lc-sidenav__logo--brand\"\n [class.lc-sidenav__logo--size-xs]=\"logoSize() === 'xs'\"\n [class.lc-sidenav__logo--size-sm]=\"logoSize() === 'sm'\"\n [class.lc-sidenav__logo--size-md]=\"logoSize() === 'md'\"\n [class.lc-sidenav__logo--size-lg]=\"logoSize() === 'lg'\"\n [class.lc-sidenav__logo--size-xl]=\"logoSize() === 'xl'\"\n >\n @if (brandHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a\n [href]=\"brandHref()\"\n class=\"lc-sidenav__brand\"\n [attr.aria-label]=\"brandAriaLabel() || null\"\n [attr.data-tooltip]=\"brandName()\"\n (click)=\"handleBrandClick()\">\n <ng-container [ngTemplateOutlet]=\"brandContent\" />\n </a>\n } @else {\n <a\n routerLink=\"/\"\n class=\"lc-sidenav__brand\"\n [attr.aria-label]=\"brandAriaLabel() || null\"\n [attr.data-tooltip]=\"brandName()\"\n (click)=\"handleBrandClick()\">\n <ng-container [ngTemplateOutlet]=\"brandContent\" />\n </a>\n }\n </div>\n } @else if (showLogo()) {\n <!-- Logo area (sidebar-first layout) -->\n <div\n class=\"lc-sidenav__logo\"\n [class.lc-sidenav__logo--size-xs]=\"logoSize() === 'xs'\"\n [class.lc-sidenav__logo--size-sm]=\"logoSize() === 'sm'\"\n [class.lc-sidenav__logo--size-md]=\"logoSize() === 'md'\"\n [class.lc-sidenav__logo--size-lg]=\"logoSize() === 'lg'\"\n [class.lc-sidenav__logo--size-xl]=\"logoSize() === 'xl'\"\n >\n <button type=\"button\" class=\"lc-sidenav__logo-link\" [attr.aria-label]=\"toggleAriaLabel()\" (click)=\"toggleCollapsed()\">\n <lc-logo\n [variant]=\"collapsed() ? 'emblem' : 'full'\"\n [size]=\"collapsed() ? 'sm' : logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || 'Logo'\"\n />\n </button>\n @if (!collapsed() && effectiveMode() === 'docked') {\n <button\n type=\"button\"\n class=\"lc-sidenav__logo-toggle\"\n (click)=\"toggleCollapsed()\"\n [attr.aria-label]=\"collapseAriaLabel() || (position() === 'right' ? 'Collapse navigation to the right' : 'Collapse navigation to the left')\"\n [attr.title]=\"collapseTitle()\">\n <lc-icon\n [name]=\"position() === 'right' ? 'chevron-right' : 'chevron-left'\"\n size=\"sm\"\n [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n\n <!-- Close button (drawer mode only) -->\n @if (effectiveMode() === 'drawer') {\n <button\n type=\"button\"\n class=\"lc-sidenav__close\"\n cdkFocusInitial\n (click)=\"handleClose()\"\n [attr.aria-label]=\"closeAriaLabel()\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\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 <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n\n <!-- Navigation items (if provided) -->\n @if (items().length > 0) {\n <nav\n class=\"lc-sidenav__nav\"\n [class.lc-sidenav__nav--docked]=\"effectiveMode() === 'docked'\"\n [attr.aria-label]=\"ariaLabel()\">\n @for (item of sortedItems(); track item.id) {\n <!-- Section headline -->\n @if (item.isSection) {\n <div class=\"lc-sidenav__section\">\n <div class=\"lc-sidenav__section-header\">\n <h2 class=\"lc-sidenav__section-title\">{{ item.label }}</h2>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (item.children?.length) {\n <ul class=\"lc-sidenav__section-items\">\n @for (child of item.children; track child.id) {\n <li>\n @if (child.children?.length) {\n <!-- Section child with nested children (collapsible) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--parent\"\n [class.lc-sidenav__nav-item--expanded]=\"isExpanded(child)\"\n [class.lc-sidenav__nav-item--has-active-child]=\"hasActiveChild(child)\"\n (click)=\"toggleExpanded(child)\"\n [attr.aria-expanded]=\"isExpanded(child)\"\n [title]=\"collapsed() && !child.badge ? child.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? child.label : null\">\n @if (child.icon) {\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" class=\"lc-sidenav__nav-chevron\" />\n </button>\n @if (child.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"child.action.ariaLabel || child.label + ' action'\"\n (click)=\"handleItemAction($event, child)\">\n <lc-icon [name]=\"child.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (isExpanded(child)) {\n <div class=\"lc-sidenav__nav-children\">\n @for (grandchild of child.children; track grandchild.id) {\n <a\n [routerLink]=\"grandchild.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(grandchild)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(grandchild)\"\n [attr.aria-current]=\"isItemActive(grandchild) ? 'page' : null\">\n @if (grandchild.icon) {\n <lc-icon [name]=\"grandchild.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ grandchild.label }}</span>\n @if (grandchild.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"grandchild.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ grandchild.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(grandchild) }}</span>\n }\n </a>\n }\n </div>\n }\n } @else {\n <!-- Section child (simple link) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <a\n [routerLink]=\"child.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(child)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(child)\"\n [attr.aria-current]=\"isItemActive(child) ? 'page' : null\"\n [title]=\"collapsed() && !child.badge ? child.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? child.label : null\">\n @if (child.icon) {\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n </a>\n @if (child.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"child.action.ariaLabel || child.label + ' action'\"\n (click)=\"handleItemAction($event, child)\">\n <lc-icon [name]=\"child.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n </div>\n } @else if (item.children?.length) {\n <!-- Parent item with children (collapsible group) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--parent\"\n [class.lc-sidenav__nav-item--expanded]=\"isExpanded(item)\"\n [class.lc-sidenav__nav-item--has-active-child]=\"hasActiveChild(item)\"\n (click)=\"toggleExpanded(item)\"\n [attr.aria-expanded]=\"isExpanded(item)\"\n [title]=\"collapsed() && !item.badge ? item.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? item.label : null\">\n <lc-icon [name]=\"item.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ item.label }}</span>\n @if (item.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"item.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ item.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(item) }}</span>\n }\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" class=\"lc-sidenav__nav-chevron\" />\n </button>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (isExpanded(item)) {\n <div class=\"lc-sidenav__nav-children\">\n @for (child of item.children; track child.id) {\n <a\n [routerLink]=\"child.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(child)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(child)\"\n [attr.aria-current]=\"isItemActive(child) ? 'page' : null\">\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n </a>\n }\n </div>\n }\n } @else {\n <!-- Simple item without children -->\n <div class=\"lc-sidenav__nav-item-row\">\n <a\n [routerLink]=\"item.route\"\n class=\"lc-sidenav__nav-item\"\n (click)=\"handleItemClick(item)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(item)\"\n [attr.aria-current]=\"isItemActive(item) ? 'page' : null\"\n [title]=\"collapsed() && !item.badge ? item.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? item.label : null\">\n <lc-icon [name]=\"item.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ item.label }}</span>\n @if (item.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"item.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ item.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(item) }}</span>\n }\n </a>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n }\n </nav>\n }\n\n <!-- Content projection (for custom content) -->\n <div class=\"lc-sidenav__content\" [class.lc-sidenav__content--docked]=\"effectiveMode() === 'docked'\">\n <ng-content></ng-content>\n </div>\n\n <!-- Collapse button at the bottom of the rail (019, docked only): on the icon column in both states. -->\n @if (showCollapseToggle() && effectiveMode() === 'docked') {\n <div class=\"lc-sidenav__collapse-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__collapse-toggle\"\n (click)=\"toggleCollapsed()\"\n [attr.aria-label]=\"collapseToggleLabel()\">\n <lc-icon\n [name]=\"collapseToggleIcon()\"\n size=\"sm\"\n [decorative]=\"true\" />\n </button>\n </div>\n }\n </aside>\n</ng-template>\n\n<!-- The brand link's content: the tile (decorative) and the name. -->\n<ng-template #brandContent>\n <span class=\"lc-sidenav__brand-tile\" aria-hidden=\"true\">\n @if (brandIcon()) {\n <lc-icon [name]=\"brandIcon()\" size=\"sm\" [decorative]=\"true\" />\n } @else {\n {{ brandInitial() }}\n }\n </span>\n <span class=\"lc-sidenav__brand-name\" [class.lc-sidenav__sr-only]=\"isRail()\">{{ brandName() }}</span>\n</ng-template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-sidenav.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/sidenav/sidenav.component.ts","../../../../libs/angular-ui-kit/src/lib/sidenav/sidenav.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n signal,\n computed,\n input,\n output,\n model,\n effect,\n untracked,\n HostListener,\n OnDestroy,\n inject,\n afterNextRender,\n EnvironmentInjector,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { NgTemplateOutlet, NgStyle } from '@angular/common';\nimport { NavigationEnd, Router, RouterLink } from '@angular/router';\nimport { filter } from 'rxjs';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { NavigationItem } from '@lifecorner/angular-ui-kit/models';\nimport {\n currentItemParents,\n currentNavigationItem,\n navigationLinks,\n navigationPath,\n sortNavigationItems,\n} from '@lifecorner/angular-ui-kit/internal/navigation';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT } from '@lifecorner/angular-ui-kit/icons';\nimport { BadgeComponent } from '@lifecorner/angular-ui-kit/badge';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\nimport { focusReturnTarget } from '@lifecorner/angular-ui-kit/internal/overlay';\n\nexport type SidenavPosition = 'left' | 'right';\nexport type SidenavMode = 'drawer' | 'docked';\n\n/**\n * Surface of the docked sidenav: `default` is the rail on `--color-sidebar`\n * with a border; `transparent` has no background and no border and draws the\n * items as 48 px pills (the side navigation on the page, \"Weite\"). A drawer\n * keeps its surface in both.\n */\nexport type SidenavVariant = 'default' | 'transparent';\n\n/**\n * Sidenav component for application navigation sidebar.\n *\n * Features:\n * - Drawer (overlay) and docked (persistent) modes\n * - Responsive mobile mode — docked automatically switches to drawer below a configurable breakpoint\n * - Collapsed icon-rail mode (narrow 56px sidebar with icons only and hover tooltips)\n * - Integrated logo area with collapse-toggle (sidebar-first layouts)\n * - Text brand (`brandName`, `brandIcon`): an icon tile and the product name,\n * one link to home (`brandHref`, else `routerLink=\"/\"`), as high as the\n * `lc-header` next to it; the rail shows only the tile. It never toggles:\n * `showCollapseToggle` adds a collapse button at the bottom of the rail\n * - Hierarchical navigation with collapsible groups (up to 3 levels)\n * - Section headlines for item grouping with optional action buttons\n * - Action buttons on any item (hover-reveal)\n * - Badge support on items (count / status indicator), read after the label\n * through `badgeAriaLabel` (3.31); on the collapsed rail the label and the\n * badge text name the item\n * - One current item, announced and painted alike: the link for `activeRoute`\n * (or, without it, the one the router's URL matches) gets\n * `aria-current=\"page\"` and the current look, every other link neither\n * (AS-16). No hover color change on the current item\n * - Keyboard navigation support (Escape closes drawer)\n * - Configurable width and position (left/right)\n * - Optional backdrop overlay\n * - Theme variants: auto, light, dark (with teal accent for dark mode). The\n * theming hooks (`--lc-sidenav-bg`, `-fg`, `-fg-active`, `-border`,\n * `-hover-bg`, `-section-fg`, `-active-bg`, `-active-fg`, `-active-icon`,\n * `-badge-ring`) can be set on `lc-sidenav` or any ancestor (3.15); a value\n * on the element wins over an ancestor's, and both win over `theme` (3.14).\n * Set the same ones for `lc-sidenav-placeholder`, e.g. on a shared ancestor;\n * `--lc-sidenav-active-indicator`\n * (the current item's 2 px bar, default `--color-primary-500`; on a\n * `theme=\"light\"` rail #208497 on any page, like its focus rings),\n * `--lc-sidenav-brand-tile-bg` (default `--color-primary-fill`) and\n * `--lc-sidenav-brand-tile-fg` (default `--color-on-primary`) can be set on\n * any ancestor\n * - `variant=\"transparent\"`: no rail background or border, items as 48 px\n * pills with 16 px labels, the current one on `--color-surface-selected`\n * - Items are at least 44 px high in every density; the current item's ink is\n * `--color-on-surface-selected` (AA on the selected tint)\n * - Accessible: the inner `<nav>` is the navigation landmark; in drawer mode\n * the panel is a modal dialog (`role=\"dialog\"`, focus moved to the close\n * button on open, trapped while open, restored on close); `returnFocusTo`\n * (3.35) names the element that gets the focus back after the drawer\n * closed (WebKit doesn't focus a tapped button)\n *\n * @example\n * ```html\n * <lc-sidenav\n * [isOpen]=\"isOpen\"\n * mode=\"docked\"\n * [(collapsed)]=\"isCollapsed\"\n * [showLogo]=\"true\"\n * logoSize=\"md\"\n * [mobileBreakpoint]=\"768\"\n * [items]=\"navItems\"\n * activeRoute=\"/dashboard\"\n * theme=\"dark\"\n * (closed)=\"isOpen = false\"\n * (itemClicked)=\"navigate($event)\"\n * (itemAction)=\"onAction($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-sidenav',\n standalone: true,\n imports: [NgTemplateOutlet, NgStyle, RouterLink, A11yModule, IconComponent, BadgeComponent, LogoComponent],\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_DOWN, ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT])],\n templateUrl: './sidenav.component.html',\n styleUrl: './sidenav.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.lc-sidenav-container]': 'true',\n '[class.lc-sidenav-container--docked]': \"effectiveMode() === 'docked'\",\n '[class.lc-sidenav-container--open]': 'isOpen()',\n // `theme` on the host (019): the themed values are private host variables\n // that the public `--lc-sidenav-*` hooks fall back to, so an app's hooks\n // on `lc-sidenav` win over `theme=\"dark\"` / `\"light\"`.\n '[class.lc-sidenav-container--light]': \"theme() === 'light'\",\n '[class.lc-sidenav-container--dark]': \"theme() === 'dark'\",\n },\n})\nexport class SidenavComponent implements OnDestroy {\n private readonly router = inject(Router);\n\n /**\n * The router's URL, updated on every `NavigationEnd`. Before the first\n * navigation it is the router's initial URL (`/`). Read only when\n * `activeRoute` is empty.\n */\n private readonly routerUrl = signal(this.router.url);\n\n /** Whether the sidenav is collapsed to icon-only rail (two-way bindable) */\n readonly collapsed = model(false);\n\n /** Whether to show the logo at the top of the sidenav */\n readonly showLogo = input(false);\n\n /** Custom URL for the full brand logo (forwarded to inner `<lc-logo>`). */\n readonly logoSrc = input('');\n /** Custom URL for the emblem-only brand logo. */\n readonly logoEmblemSrc = input('');\n /** Optional dark-theme URL for the full brand logo. */\n readonly logoDarkSrc = input('');\n /** Optional dark-theme URL for the emblem-only brand logo. */\n readonly logoDarkEmblemSrc = input('');\n /** Alt text for the brand logo. */\n readonly logoAlt = input('');\n\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md` for a prominent brand block aligned with `<lc-header size=\"md\">` (both 64px). Also sets the height of the text brand (`brandName`). */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n\n /**\n * Product name shown as a text brand at the top: an icon tile and the name,\n * one link to home. Replaces the image logo (`showLogo`). The block is as\n * high as the image logo (`logoSize`: 64 px for `md`, matching\n * `lc-header size=\"md\"`); the name wraps to two lines at most, and the full\n * name stays the link's accessible name. Collapsed, only the tile shows.\n * The brand never toggles the rail (see `showCollapseToggle`). Under\n * `@defer`, give the `lc-sidenav-placeholder` `[showLogo]=\"true\"` and the\n * same `logoSize` to reserve the block. Empty: no text brand.\n * @default ''\n */\n readonly brandName = input('');\n\n /**\n * `lc-icon` name in the brand tile (decorative). Empty: the tile shows the\n * name's first letter. Renders nothing without `brandName`.\n * @default ''\n */\n readonly brandIcon = input('');\n\n /**\n * Plain URL of the brand link (a native `<a href>`, works without the\n * router). Empty: `routerLink=\"/\"`, as `lc-header`'s `homeHref`.\n * @default ''\n */\n readonly brandHref = input('');\n\n /**\n * Accessible name of the brand link. Empty: the brand name. Start it with\n * the visible name, e.g. `'Finance Intelligence – home'`.\n * @default ''\n */\n readonly brandAriaLabel = input('');\n\n /**\n * Docked mode: a button at the bottom of the rail collapses and expands it\n * (`collapsed`). Independent of the brand. With `showLogo` the image logo\n * toggles already, so use one of the two.\n * @default false\n */\n readonly showCollapseToggle = input(false);\n\n /**\n * Accessible name of the collapse button (`showCollapseToggle`) while the\n * rail is collapsed. Expanded, it uses `collapseAriaLabel`.\n * @default 'Expand navigation'\n */\n readonly expandAriaLabel = input('Expand navigation');\n\n /** Whether the sidenav is open */\n readonly isOpen = input(false);\n\n /** Display mode: 'drawer' (overlay) or 'docked' (persistent sidebar) */\n readonly mode = input<SidenavMode>('drawer');\n\n /** Mobile breakpoint in pixels. Below this width, docked mode switches to drawer. */\n readonly mobileBreakpoint = input(768);\n\n /** Whether the viewport is below the mobile breakpoint */\n readonly isMobile = signal(false);\n\n /** Effective mode: switches docked → drawer on mobile */\n readonly effectiveMode = computed(() => {\n if (this.isMobile() && this.mode() === 'docked') {\n return 'drawer' as SidenavMode;\n }\n return this.mode();\n });\n\n private mediaQuery: MediaQueryList | null = null;\n private mediaHandler = (e: MediaQueryListEvent) => this.isMobile.set(e.matches);\n\n /** Position of the sidenav (left or right) */\n readonly position = input<SidenavPosition>('left');\n\n /** Width of the sidenav drawer */\n readonly width = input('320px');\n\n /**\n * ARIA label of the navigation landmark (the inner `<nav>`) and, in drawer\n * mode, of the dialog panel.\n */\n readonly ariaLabel = input('Side navigation');\n\n /** Whether to show the overlay backdrop */\n readonly hasOverlay = input(true);\n\n /**\n * Drawer mode only (`mode=\"drawer\"`, or `docked` below `mobileBreakpoint`):\n * element that gets the focus back after the drawer closed (3.35), instead\n * of the one that had it at open. Set it to the trigger: WebKit (Safari,\n * every iOS browser) doesn't focus a tapped button, so the captured element\n * is `body`. Applied after the next render, once the panel has left the DOM\n * and the focus trap has restored its captured element. Holds on ×,\n * Escape, backdrop, an item or brand tap on a phone (they close it through\n * `closed`), `isOpen` false, and when the sidenav is destroyed while open.\n * A custom-element host that can't take focus (`<lc-button>`) hands it to\n * its first focusable descendant. Ignored when the element is no longer in\n * the document or can't take focus; the captured element then keeps it.\n * `null`: as before.\n * @default null\n */\n readonly returnFocusTo = input<HTMLElement | null>(null);\n\n /**\n * Navigation items (`NavigationItem[]`): `id`, `icon`, `label`, `route`,\n * `displayOrder` (sort order, 1 = first); optional `children`\n * (`NavigationItem[]`, a collapsible group, up to 3 levels), `isSection`\n * (renders `label` as a section headline over its `children`), `badge`\n * (`{ value: string | number; variant?: 'default' | 'primary' | 'success' |\n * 'warning' | 'error' | 'info' }`), `action` (`{ icon: string; ariaLabel?:\n * string }`, a button that emits `itemAction`), `requiredRole` (for the\n * app's own filtering; not evaluated here). Items link with `routerLink`;\n * exactly one link, the current item (see `activeRoute`), gets\n * `aria-current=\"page\"`.\n * @default []\n */\n readonly items = input<NavigationItem[]>([]);\n\n /**\n * The current route. When set, the first link (in display order) whose\n * `route` equals it is the current item: it alone gets `aria-current=\"page\"`\n * and the current look, whatever the router matches. When empty, the\n * sidenav follows the router: the link whose route equals the URL's path is\n * current, otherwise the one with the longest route the path starts with\n * (`/household` on `/household/members`; `/` only on `/`). Routes are\n * compared as written, so give them as absolute paths (`'/household'`).\n * @default ''\n */\n readonly activeRoute = input('');\n\n /** Theme variant for the sidenav */\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n\n /**\n * Surface of the docked sidenav: `default` (the rail with background and\n * border) or `transparent` (no background, no border, items as 48 px pills\n * with radius full and 16 px labels; the current one on\n * `--color-surface-selected`). A drawer keeps its surface.\n */\n readonly variant = input<SidenavVariant>('default');\n\n /** Accessible name of the logo button that collapses/expands the sidebar. */\n readonly toggleAriaLabel = input('Toggle sidebar');\n\n /** Accessible name of the collapse button. Empty: \"Collapse navigation to the left/right\" by `position`. */\n readonly collapseAriaLabel = input('');\n\n /** Tooltip of the collapse button. */\n readonly collapseTitle = input('Collapse sidebar');\n\n /** Accessible name of the close button (drawer mode). */\n readonly closeAriaLabel = input('Close navigation');\n\n /**\n * Text read after an item's label when it has a badge, at every level. On\n * the collapsed rail, where labels are hidden, the label and this text name\n * the item. `{value}` is replaced with the badge value, e.g.\n * `'{value} offen'` → \"Stammdaten 1 offen\". The badge itself is hidden from\n * screen readers. Empty: the badge isn't read. Since 3.33 a badged rail\n * item has no `title` (it repeated the label as a description); its\n * tooltip still shows on hover and focus.\n *\n * @default '{value}'\n */\n readonly badgeAriaLabel = input('{value}');\n\n /**\n * Ids of the expanded parent items — the single source of truth for\n * `isExpanded`. Seeded with the current item's parent whenever it changes\n * (see constructor), so a group opens on navigation but can still\n * be collapsed by the user. (It used to be OR-ed with `hasActiveChild` at\n * read time, so toggling such a group flipped the set without ever closing\n * it — the set drifted out of sync with what was on screen.)\n */\n readonly expandedItems = signal<Set<string>>(new Set());\n\n /** Ids of the collapsible parents (top-level groups, section sub-groups) that contain the current item. */\n private readonly activeChildParents = computed(() => currentItemParents(this.items(), this.currentItem()));\n\n /**\n * Computed sorted navigation items by displayOrder\n */\n readonly sortedItems = computed(() => sortNavigationItems(this.items()));\n\n /**\n * Every item rendered as a link, in display order (depth first): simple\n * items, the children of a group, the children of a section, and the\n * children of a section's sub-group.\n */\n private readonly linkItems = computed(() => navigationLinks(this.sortedItems()));\n\n /** The router URL's path: primary outlet only, no query, fragment or matrix parameters. */\n private readonly routerPath = computed(() => navigationPath(this.router, this.routerUrl()));\n\n /**\n * The one current link (or none). The single source of both the current\n * look (`lc-sidenav__nav-item--active`) and `aria-current=\"page\"` (AS-16).\n * With `activeRoute` set: the first link whose route equals it. Without:\n * the router's path — the exact match wins, otherwise the longest link\n * route the path continues with a `/` (`/` matches only `/`); ties go to\n * the first link in display order. The rules live in\n * `internal/navigation` since 3.6, shared with `lc-sidenav-placeholder`.\n */\n private readonly currentItem = computed<NavigationItem | null>(() =>\n currentNavigationItem(this.linkItems(), this.activeRoute(), this.activeRoute() === '' ? this.routerPath() : ''),\n );\n\n /**\n * Event emitted when the sidenav should close\n */\n readonly closed = output<void>();\n\n /**\n * Event emitted when a navigation item is clicked\n */\n readonly itemClicked = output<NavigationItem>();\n\n /**\n * Event emitted when an item's action button is clicked\n */\n readonly itemAction = output<NavigationItem>();\n\n /** Whether the drawer panel is rendered (drawer mode and open). */\n private readonly drawerOpen = computed(() => this.isOpen() && this.effectiveMode() === 'drawer');\n /** Whether the drawer panel was shown without a close after it. */\n private drawerShown = false;\n /**\n * The environment injector: a node injector would cancel the focus-return\n * hook when the sidenav is destroyed while open.\n */\n private readonly environmentInjector = inject(EnvironmentInjector);\n\n constructor() {\n this.router.events\n .pipe(\n filter((event): event is NavigationEnd => event instanceof NavigationEnd),\n takeUntilDestroyed(),\n )\n .subscribe(() => this.routerUrl.set(this.router.url));\n\n // React to mobileBreakpoint changes\n effect(() => {\n this.mobileBreakpoint(); // track dependency\n this.setupMediaQuery();\n });\n\n // Navigating into a group opens it: add the active child's parent(s) to\n // the expanded set. Only additive — the user's own collapses/expands of\n // other groups survive a route change.\n effect(() => {\n const parents = this.activeChildParents();\n if (parents.length === 0) return;\n const expanded = untracked(this.expandedItems);\n if (parents.every((id) => expanded.has(id))) return;\n this.expandedItems.set(new Set([...expanded, ...parents]));\n });\n\n // 3.35: `returnFocusTo` after the drawer panel closed. Untracked: a\n // change of the input alone must not schedule anything.\n effect(() => {\n const open = this.drawerOpen();\n untracked(() => {\n if (open) {\n this.drawerShown = true;\n } else {\n this.drawerClosed();\n }\n });\n });\n }\n\n ngOnDestroy(): void {\n this.teardownMediaQuery();\n this.drawerClosed();\n }\n\n /** Ends a shown drawer panel; schedules `returnFocusTo` when set. */\n private drawerClosed(): void {\n if (!this.drawerShown) return;\n this.drawerShown = false;\n if (this.returnFocusTo()) this.scheduleFocusReturn();\n }\n\n /**\n * Focuses `returnFocusTo` after the next render: the template refresh has\n * removed the panel, and `cdkTrapFocus` has restored its captured element\n * on destroy, so this is the last focus write. Reads the input again then,\n * so a trigger re-rendered meanwhile is taken from a fresh query.\n */\n private scheduleFocusReturn(): void {\n afterNextRender(\n () => {\n const target = this.returnFocusTo();\n if (this.drawerShown || !target?.isConnected) return;\n focusReturnTarget(target);\n },\n { injector: this.environmentInjector },\n );\n }\n\n private setupMediaQuery(): void {\n if (typeof window === 'undefined') return;\n this.teardownMediaQuery();\n this.mediaQuery = window.matchMedia(`(max-width: ${this.mobileBreakpoint()}px)`);\n this.isMobile.set(this.mediaQuery.matches);\n this.mediaQuery.addEventListener('change', this.mediaHandler);\n }\n\n private teardownMediaQuery(): void {\n this.mediaQuery?.removeEventListener('change', this.mediaHandler);\n this.mediaQuery = null;\n }\n\n /**\n * Computed CSS classes for the sidenav\n */\n sidenavClasses = computed(() => {\n const classes = ['lc-sidenav'];\n classes.push(`lc-sidenav--${this.position()}`);\n classes.push(`lc-sidenav--${this.effectiveMode()}`);\n if (this.isOpen()) {\n classes.push('lc-sidenav--open');\n }\n if (this.collapsed() && !this.isMobile()) {\n classes.push('lc-sidenav--collapsed');\n }\n if (this.theme() !== 'auto') {\n classes.push(`lc-sidenav--${this.theme()}`);\n }\n if (this.variant() === 'transparent') {\n classes.push('lc-sidenav--transparent');\n }\n return classes.join(' ');\n });\n\n /**\n * Computed inline styles for the sidenav\n */\n sidenavStyles = computed(() => ({\n width: this.isMobile() ? this.width() : (this.collapsed() ? '56px' : this.width()),\n }));\n\n /** The brand tile's letter when there is no `brandIcon`: the name's first character, upper-cased. */\n protected readonly brandInitial = computed(() => ([...this.brandName().trim()][0] ?? '').toUpperCase());\n\n /** Whether the rail shows only icons (as `lc-sidenav--collapsed`). */\n protected readonly isRail = computed(() => this.collapsed() && !this.isMobile());\n\n /** Visually hidden badge text: `badgeAriaLabel` with `{value}`; on the rail, where labels are hidden, after the label. */\n protected badgeText(item: NavigationItem): string {\n if (!item.badge) return '';\n const text = this.badgeAriaLabel().replace('{value}', String(item.badge.value));\n return this.isRail() ? `${item.label} ${text}` : text;\n }\n\n /** The collapse button's accessible name: what a press does. */\n protected readonly collapseToggleLabel = computed(() =>\n this.collapsed()\n ? this.expandAriaLabel()\n : this.collapseAriaLabel() ||\n (this.position() === 'right' ? 'Collapse navigation to the right' : 'Collapse navigation to the left'),\n );\n\n /** The collapse button's chevron points where the rail moves: ⟨ to collapse a left rail, ⟩ to expand it (mirrored on the right). */\n protected readonly collapseToggleIcon = computed(() => {\n const towardsLeft = this.position() === 'right' ? this.collapsed() : !this.collapsed();\n return towardsLeft ? 'chevron-left' : 'chevron-right';\n });\n\n /**\n * Toggle collapsed state\n */\n toggleCollapsed(): void {\n this.collapsed.set(!this.collapsed());\n }\n\n /** The brand link closes the drawer on a phone, as the items do. */\n handleBrandClick(): void {\n if (this.isMobile()) {\n this.handleClose();\n }\n }\n\n /**\n * Handle close action\n */\n handleClose(): void {\n this.closed.emit();\n }\n\n /**\n * Handle navigation item click\n */\n handleItemClick(item: NavigationItem): void {\n this.itemClicked.emit(item);\n // Auto-close drawer on mobile after navigation\n if (this.isMobile()) {\n this.handleClose();\n }\n }\n\n /**\n * Handle action button click on a navigation item\n */\n handleItemAction(event: Event, item: NavigationItem): void {\n event.stopPropagation();\n this.itemAction.emit(item);\n }\n\n /**\n * Whether the item is the current link (see `currentItem`): it alone is\n * painted current and carries `aria-current=\"page\"`.\n */\n isItemActive(item: NavigationItem): boolean {\n return this.currentItem() === item;\n }\n\n /**\n * Check if a parent item contains the current link\n */\n hasActiveChild(item: NavigationItem): boolean {\n const current = this.currentItem();\n return !!current && !!item.children?.includes(current);\n }\n\n /**\n * Toggle expansion of a parent item.\n * If the sidenav is collapsed, expand it first.\n */\n toggleExpanded(item: NavigationItem): void {\n if (this.collapsed()) {\n this.collapsed.set(false);\n // Expand the clicked item after uncollapsing\n const expanded = new Set(this.expandedItems());\n expanded.add(item.id);\n this.expandedItems.set(expanded);\n return;\n }\n const expanded = new Set(this.expandedItems());\n if (expanded.has(item.id)) {\n expanded.delete(item.id);\n } else {\n expanded.add(item.id);\n }\n this.expandedItems.set(expanded);\n }\n\n /**\n * Check if an item is expanded\n */\n isExpanded(item: NavigationItem): boolean {\n return this.expandedItems().has(item.id);\n }\n\n /**\n * Handle keyboard navigation. Keyed on the EFFECTIVE mode: a docked sidenav\n * that has become a drawer below the mobile breakpoint must close on Escape\n * like any drawer.\n */\n @HostListener('document:keydown', ['$event'])\n handleKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.isOpen() && this.effectiveMode() === 'drawer') {\n this.handleClose();\n }\n }\n}\n","<!-- Drawer mode: render with overlay and conditional visibility -->\n@if (effectiveMode() === 'drawer') {\n @if (isOpen()) {\n <!-- Overlay backdrop -->\n @if (hasOverlay()) {\n <div\n class=\"lc-sidenav__overlay\"\n (click)=\"handleClose()\"\n aria-hidden=\"true\">\n </div>\n }\n\n <ng-container *ngTemplateOutlet=\"sidenavPanel\" />\n }\n}\n\n<!-- Docked mode: always render, visibility controlled by CSS -->\n@if (effectiveMode() === 'docked') {\n <ng-container *ngTemplateOutlet=\"sidenavPanel\" />\n}\n\n<!-- Shared sidenav panel template.\n The <aside> is a plain complementary region; the navigation landmark is\n the inner <nav> (nesting a role=\"navigation\" wrapper around a <nav> gave\n two navigation landmarks). In drawer mode the panel is a modal dialog:\n focus is captured (close button first, see cdkFocusInitial), trapped\n while open and restored to the opener when the panel is destroyed. -->\n<ng-template #sidenavPanel>\n <aside\n [class]=\"sidenavClasses()\"\n [ngStyle]=\"sidenavStyles()\"\n [attr.role]=\"effectiveMode() === 'drawer' ? 'dialog' : null\"\n [attr.aria-modal]=\"effectiveMode() === 'drawer' ? 'true' : null\"\n [attr.aria-label]=\"effectiveMode() === 'drawer' ? ariaLabel() : null\"\n [cdkTrapFocus]=\"effectiveMode() === 'drawer'\"\n [cdkTrapFocusAutoCapture]=\"effectiveMode() === 'drawer'\">\n\n <!-- Text brand (019): one link to home, never a toggle. Replaces the image logo. -->\n @if (brandName()) {\n <div\n class=\"lc-sidenav__logo lc-sidenav__logo--brand\"\n [class.lc-sidenav__logo--size-xs]=\"logoSize() === 'xs'\"\n [class.lc-sidenav__logo--size-sm]=\"logoSize() === 'sm'\"\n [class.lc-sidenav__logo--size-md]=\"logoSize() === 'md'\"\n [class.lc-sidenav__logo--size-lg]=\"logoSize() === 'lg'\"\n [class.lc-sidenav__logo--size-xl]=\"logoSize() === 'xl'\"\n >\n @if (brandHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a\n [href]=\"brandHref()\"\n class=\"lc-sidenav__brand\"\n [attr.aria-label]=\"brandAriaLabel() || null\"\n [attr.data-tooltip]=\"brandName()\"\n (click)=\"handleBrandClick()\">\n <ng-container [ngTemplateOutlet]=\"brandContent\" />\n </a>\n } @else {\n <a\n routerLink=\"/\"\n class=\"lc-sidenav__brand\"\n [attr.aria-label]=\"brandAriaLabel() || null\"\n [attr.data-tooltip]=\"brandName()\"\n (click)=\"handleBrandClick()\">\n <ng-container [ngTemplateOutlet]=\"brandContent\" />\n </a>\n }\n </div>\n } @else if (showLogo()) {\n <!-- Logo area (sidebar-first layout) -->\n <div\n class=\"lc-sidenav__logo\"\n [class.lc-sidenav__logo--size-xs]=\"logoSize() === 'xs'\"\n [class.lc-sidenav__logo--size-sm]=\"logoSize() === 'sm'\"\n [class.lc-sidenav__logo--size-md]=\"logoSize() === 'md'\"\n [class.lc-sidenav__logo--size-lg]=\"logoSize() === 'lg'\"\n [class.lc-sidenav__logo--size-xl]=\"logoSize() === 'xl'\"\n >\n <button type=\"button\" class=\"lc-sidenav__logo-link\" [attr.aria-label]=\"toggleAriaLabel()\" (click)=\"toggleCollapsed()\">\n <lc-logo\n [variant]=\"collapsed() ? 'emblem' : 'full'\"\n [size]=\"collapsed() ? 'sm' : logoSize()\"\n [clickable]=\"false\"\n [colorMode]=\"theme() === 'dark' ? 'dark' : theme() === 'light' ? 'light' : 'auto'\"\n [src]=\"logoSrc()\"\n [emblemSrc]=\"logoEmblemSrc()\"\n [darkSrc]=\"logoDarkSrc()\"\n [darkEmblemSrc]=\"logoDarkEmblemSrc()\"\n [alt]=\"logoAlt() || 'Logo'\"\n />\n </button>\n @if (!collapsed() && effectiveMode() === 'docked') {\n <button\n type=\"button\"\n class=\"lc-sidenav__logo-toggle\"\n (click)=\"toggleCollapsed()\"\n [attr.aria-label]=\"collapseAriaLabel() || (position() === 'right' ? 'Collapse navigation to the right' : 'Collapse navigation to the left')\"\n [attr.title]=\"collapseTitle()\">\n <lc-icon\n [name]=\"position() === 'right' ? 'chevron-right' : 'chevron-left'\"\n size=\"sm\"\n [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n\n <!-- Close button (drawer mode only) -->\n @if (effectiveMode() === 'drawer') {\n <button\n type=\"button\"\n class=\"lc-sidenav__close\"\n cdkFocusInitial\n (click)=\"handleClose()\"\n [attr.aria-label]=\"closeAriaLabel()\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\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 <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n\n <!-- Navigation items (if provided) -->\n @if (items().length > 0) {\n <nav\n class=\"lc-sidenav__nav\"\n [class.lc-sidenav__nav--docked]=\"effectiveMode() === 'docked'\"\n [attr.aria-label]=\"ariaLabel()\">\n @for (item of sortedItems(); track item.id) {\n <!-- Section headline -->\n @if (item.isSection) {\n <div class=\"lc-sidenav__section\">\n <div class=\"lc-sidenav__section-header\">\n <h2 class=\"lc-sidenav__section-title\">{{ item.label }}</h2>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (item.children?.length) {\n <ul class=\"lc-sidenav__section-items\">\n @for (child of item.children; track child.id) {\n <li>\n @if (child.children?.length) {\n <!-- Section child with nested children (collapsible) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--parent\"\n [class.lc-sidenav__nav-item--expanded]=\"isExpanded(child)\"\n [class.lc-sidenav__nav-item--has-active-child]=\"hasActiveChild(child)\"\n (click)=\"toggleExpanded(child)\"\n [attr.aria-expanded]=\"isExpanded(child)\"\n [title]=\"collapsed() && !child.badge ? child.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? child.label : null\">\n @if (child.icon) {\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" class=\"lc-sidenav__nav-chevron\" />\n </button>\n @if (child.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"child.action.ariaLabel || child.label + ' action'\"\n (click)=\"handleItemAction($event, child)\">\n <lc-icon [name]=\"child.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (isExpanded(child)) {\n <div class=\"lc-sidenav__nav-children\">\n @for (grandchild of child.children; track grandchild.id) {\n <a\n [routerLink]=\"grandchild.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(grandchild)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(grandchild)\"\n [attr.aria-current]=\"isItemActive(grandchild) ? 'page' : null\">\n @if (grandchild.icon) {\n <lc-icon [name]=\"grandchild.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ grandchild.label }}</span>\n @if (grandchild.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"grandchild.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ grandchild.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(grandchild) }}</span>\n }\n </a>\n }\n </div>\n }\n } @else {\n <!-- Section child (simple link) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <a\n [routerLink]=\"child.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(child)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(child)\"\n [attr.aria-current]=\"isItemActive(child) ? 'page' : null\"\n [title]=\"collapsed() && !child.badge ? child.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? child.label : null\">\n @if (child.icon) {\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n }\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n </a>\n @if (child.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"child.action.ariaLabel || child.label + ' action'\"\n (click)=\"handleItemAction($event, child)\">\n <lc-icon [name]=\"child.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n </div>\n } @else if (item.children?.length) {\n <!-- Parent item with children (collapsible group) -->\n <div class=\"lc-sidenav__nav-item-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--parent\"\n [class.lc-sidenav__nav-item--expanded]=\"isExpanded(item)\"\n [class.lc-sidenav__nav-item--has-active-child]=\"hasActiveChild(item)\"\n (click)=\"toggleExpanded(item)\"\n [attr.aria-expanded]=\"isExpanded(item)\"\n [title]=\"collapsed() && !item.badge ? item.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? item.label : null\">\n <lc-icon [name]=\"item.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ item.label }}</span>\n @if (item.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"item.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ item.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(item) }}</span>\n }\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" class=\"lc-sidenav__nav-chevron\" />\n </button>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n @if (isExpanded(item)) {\n <div class=\"lc-sidenav__nav-children\">\n @for (child of item.children; track child.id) {\n <a\n [routerLink]=\"child.route\"\n class=\"lc-sidenav__nav-item lc-sidenav__nav-item--child\"\n (click)=\"handleItemClick(child)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(child)\"\n [attr.aria-current]=\"isItemActive(child) ? 'page' : null\">\n <lc-icon [name]=\"child.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ child.label }}</span>\n @if (child.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"child.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ child.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(child) }}</span>\n }\n </a>\n }\n </div>\n }\n } @else {\n <!-- Simple item without children -->\n <div class=\"lc-sidenav__nav-item-row\">\n <a\n [routerLink]=\"item.route\"\n class=\"lc-sidenav__nav-item\"\n (click)=\"handleItemClick(item)\"\n [class.lc-sidenav__nav-item--active]=\"isItemActive(item)\"\n [attr.aria-current]=\"isItemActive(item) ? 'page' : null\"\n [title]=\"collapsed() && !item.badge ? item.label : ''\"\n [attr.data-tooltip]=\"collapsed() ? item.label : null\">\n <lc-icon [name]=\"item.icon\" size=\"sm\" [decorative]=\"true\" class=\"lc-sidenav__nav-icon\" />\n <span class=\"lc-sidenav__nav-label\">{{ item.label }}</span>\n @if (item.badge) {\n <lc-badge aria-hidden=\"true\" [variant]=\"item.badge.variant || 'default'\" size=\"xs\" [rounded]=\"true\" class=\"lc-sidenav__nav-badge\">{{ item.badge.value }}</lc-badge>&ngsp;<span class=\"lc-sidenav__sr-only\">{{ badgeText(item) }}</span>\n }\n </a>\n @if (item.action) {\n <button\n type=\"button\"\n class=\"lc-sidenav__action-btn\"\n [attr.aria-label]=\"item.action.ariaLabel || item.label + ' action'\"\n (click)=\"handleItemAction($event, item)\">\n <lc-icon [name]=\"item.action.icon\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </div>\n }\n }\n </nav>\n }\n\n <!-- Content projection (for custom content) -->\n <div class=\"lc-sidenav__content\" [class.lc-sidenav__content--docked]=\"effectiveMode() === 'docked'\">\n <ng-content></ng-content>\n </div>\n\n <!-- Collapse button at the bottom of the rail (019, docked only): on the icon column in both states. -->\n @if (showCollapseToggle() && effectiveMode() === 'docked') {\n <div class=\"lc-sidenav__collapse-row\">\n <button\n type=\"button\"\n class=\"lc-sidenav__collapse-toggle\"\n (click)=\"toggleCollapsed()\"\n [attr.aria-label]=\"collapseToggleLabel()\">\n <lc-icon\n [name]=\"collapseToggleIcon()\"\n size=\"sm\"\n [decorative]=\"true\" />\n </button>\n </div>\n }\n </aside>\n</ng-template>\n\n<!-- The brand link's content: the tile (decorative) and the name. -->\n<ng-template #brandContent>\n <span class=\"lc-sidenav__brand-tile\" aria-hidden=\"true\">\n @if (brandIcon()) {\n <lc-icon [name]=\"brandIcon()\" size=\"sm\" [decorative]=\"true\" />\n } @else {\n {{ brandInitial() }}\n }\n </span>\n <span class=\"lc-sidenav__brand-name\" [class.lc-sidenav__sr-only]=\"isRail()\">{{ brandName() }}</span>\n</ng-template>\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
- import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, afterNextRender, computed, inject, input, signal } from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, afterNextRender, computed, effect, inject, input, isDevMode, signal } from "@angular/core";
3
3
  import { chartColor, formatChartValue, niceScale, toFinite } from "@lifecorner/angular-ui-kit/internal/chart";
4
+ const roundPercent = (value) => Math.round(value * 1e4) / 1e4;
4
5
  const DEFAULT_WIDTH = 400;
5
6
  const MIN_SEGMENT_LABEL_PX = 14;
6
7
  var StackedBarChartComponent = class StackedBarChartComponent {
@@ -15,6 +16,10 @@ var StackedBarChartComponent = class StackedBarChartComponent {
15
16
  obs.observe(this._el.nativeElement);
16
17
  this._destroyRef.onDestroy(() => obs.disconnect());
17
18
  });
19
+ if (isDevMode()) effect(() => {
20
+ const count = (this.categories() ?? []).length;
21
+ if (this.variant() === "band" && count !== 1) console.warn(`[lc-stacked-bar-chart] variant="band" needs exactly one category (got ${count}): rendering as chart`);
22
+ });
18
23
  }
19
24
  categories = input.required(...ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []);
20
25
  legends = input([], ...ngDevMode ? [{ debugName: "legends" }] : /* istanbul ignore next */ []);
@@ -28,6 +33,28 @@ var StackedBarChartComponent = class StackedBarChartComponent {
28
33
  barGap = input(.3, ...ngDevMode ? [{ debugName: "barGap" }] : /* istanbul ignore next */ []);
29
34
  formatValue = input(formatChartValue, ...ngDevMode ? [{ debugName: "formatValue" }] : /* istanbul ignore next */ []);
30
35
  ariaLabel = input("", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
36
+ variant = input("chart", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
37
+ isBand = computed(() => this.variant() === "band" && (this.categories() ?? []).length === 1, ...ngDevMode ? [{ debugName: "isBand" }] : /* istanbul ignore next */ []);
38
+ bandParts = computed(() => {
39
+ if (!this.isBand()) return [];
40
+ const legs = this.legends();
41
+ const values = (this.categories()[0].values ?? []).map((raw) => Math.max(0, toFinite(raw)));
42
+ const sum = values.reduce((a, b) => a + b, 0);
43
+ if (sum <= 0) return [];
44
+ let at = 0;
45
+ return values.map((value, i) => {
46
+ const x = at / sum * 100;
47
+ at += value;
48
+ return {
49
+ x: roundPercent(x),
50
+ w: roundPercent(value / sum * 100),
51
+ color: legs[i]?.color || chartColor(i)
52
+ };
53
+ });
54
+ }, ...ngDevMode ? [{ debugName: "bandParts" }] : /* istanbul ignore next */ []);
55
+ bandGaps = computed(() => {
56
+ return this.bandParts().filter((part) => part.w > 0).slice(1).map((part) => part.x);
57
+ }, ...ngDevMode ? [{ debugName: "bandGaps" }] : /* istanbul ignore next */ []);
31
58
  PL = 40;
32
59
  PR = 10;
33
60
  PT = 10;
@@ -282,12 +309,19 @@ var StackedBarChartComponent = class StackedBarChartComponent {
282
309
  isSignal: true,
283
310
  isRequired: false,
284
311
  transformFunction: null
312
+ },
313
+ variant: {
314
+ classPropertyName: "variant",
315
+ publicName: "variant",
316
+ isSignal: true,
317
+ isRequired: false,
318
+ transformFunction: null
285
319
  }
286
320
  },
287
321
  host: { properties: { "style.max-width.px": "width()" } },
288
322
  ngImport: i0,
289
- template: "<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
290
- styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}\n"],
323
+ template: "<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
324
+ styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}.lc-stacked-bar-chart__svg--band{overflow:hidden}.lc-stacked-bar-chart__band-gap{stroke:var(--lc-stacked-bar-chart-band-gap-color, var(--color-surface));stroke-width:var(--lc-stacked-bar-chart-band-gap, 2px)}@media(forced-colors:active){.lc-stacked-bar-chart__band-gap{stroke:Canvas}}\n"],
291
325
  changeDetection: i0.ChangeDetectionStrategy.OnPush
292
326
  });
293
327
  };
@@ -303,8 +337,8 @@ i0.ɵɵngDeclareClassMetadata({
303
337
  standalone: true,
304
338
  changeDetection: ChangeDetectionStrategy.OnPush,
305
339
  host: { "[style.max-width.px]": "width()" },
306
- template: "<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
307
- styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}\n"]
340
+ template: "<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
341
+ styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}.lc-stacked-bar-chart__svg--band{overflow:hidden}.lc-stacked-bar-chart__band-gap{stroke:var(--lc-stacked-bar-chart-band-gap-color, var(--color-surface));stroke-width:var(--lc-stacked-bar-chart-band-gap, 2px)}@media(forced-colors:active){.lc-stacked-bar-chart__band-gap{stroke:Canvas}}\n"]
308
342
  }]
309
343
  }],
310
344
  ctorParameters: () => [],
@@ -404,6 +438,14 @@ i0.ɵɵngDeclareClassMetadata({
404
438
  alias: "ariaLabel",
405
439
  required: false
406
440
  }]
441
+ }],
442
+ variant: [{
443
+ type: i0.Input,
444
+ args: [{
445
+ isSignal: true,
446
+ alias: "variant",
447
+ required: false
448
+ }]
407
449
  }]
408
450
  }
409
451
  });
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-stacked-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n inject,\n input,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n ChartValueFormatter,\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface StackedBarCategory {\n label: string;\n /** One value per series. Negative values stack downward (left) from the zero baseline. */\n values: number[];\n}\n\nexport interface StackedBarLegend {\n label: string;\n color?: string;\n}\n\nexport type StackedBarOrientation = 'vertical' | 'horizontal';\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n/** Segments shorter than this along the value axis get no inline value label. */\nconst MIN_SEGMENT_LABEL_PX = 14;\n\n@Component({\n selector: 'lc-stacked-bar-chart',\n standalone: true,\n templateUrl: './stacked-bar-chart.component.html',\n styleUrls: ['./stacked-bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Stacked bar chart component for comparing category compositions.\n *\n * Features:\n * - Multiple stacked value segments per category\n * - Vertical and horizontal orientation\n * - Negative values stacked downward from a zero baseline\n * - Optional legend, grid, value labels (segments + totals), and axis labels\n * - Configurable bar gap spacing\n * - Color-coded segments with legend mapping\n * - Responsive SVG rendering\n *\n * @example\n * ```html\n * <lc-stacked-bar-chart [categories]=\"data\" [legends]=\"legends\" [showLegend]=\"true\" />\n * ```\n */\nexport class StackedBarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n }\n\n /** Categories with stacked values. */\n categories = input.required<StackedBarCategory[]>();\n\n /** Legend items mapping to each value index. */\n legends = input<StackedBarLegend[]>([]);\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n */\n width = input<number | undefined>(undefined);\n height = input<number>(200);\n orientation = input<StackedBarOrientation>('vertical');\n showLabels = input<boolean>(true);\n /** Show the value inside each segment (when it fits) and the total at the end of each stack. */\n showValues = input<boolean>(false);\n showLegend = input<boolean>(true);\n showGrid = input<boolean>(true);\n barGap = input<number>(0.3);\n\n /** Formats segment/total value labels and axis ticks. Defaults to a float-safe `String(value)`. */\n formatValue = input<ChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary listing the\n * series names and every category's total.\n */\n ariaLabel = input<string>('');\n\n private readonly PL = 40;\n private readonly PR = 10;\n private readonly PT = 10;\n private readonly PB = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(() => `0 0 ${this.effectiveWidth()} ${this.height()}`);\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n private readonly plot = computed(() => ({\n x: this.PL,\n y: this.PT,\n w: this.effectiveWidth() - this.PL - this.PR,\n h: this.height() - this.PT - this.PB,\n }));\n\n /** Positive and negative stack extents per category (positives and negatives stack separately). */\n private readonly extents = computed(() =>\n (this.categories() ?? []).map((c) => {\n let pos = 0;\n let neg = 0;\n for (const raw of c.values ?? []) {\n const v = toFinite(raw);\n if (v >= 0) pos += v;\n else neg += v;\n }\n return { pos, neg, total: pos + neg };\n })\n );\n\n /**\n * One scale for grid *and* segments: nice tick bounds that always include\n * zero and extend below it when a stack goes negative.\n */\n protected readonly scale = computed(() => {\n const ext = this.extents();\n const max = Math.max(0, ...ext.map((e) => e.pos));\n const min = Math.min(0, ...ext.map((e) => e.neg));\n return niceScale(min, max, 4);\n });\n\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.categories()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({ pos: this.valueToPos(tick), label: fmt(tick) }));\n });\n\n protected readonly stacks = computed(() => {\n const cats = this.categories();\n const legs = this.legends();\n if (!cats?.length) return [];\n\n const p = this.plot();\n const h = this.height();\n const isV = this.isVertical();\n const gap = this.barGap();\n const fmt = this.formatValue();\n const ext = this.extents();\n\n const slot = (isV ? p.w : p.h) / cats.length;\n const thickness = slot * (1 - gap);\n const off = (slot - thickness) / 2;\n\n return cats.map((cat, ci) => {\n const along = (isV ? p.x : p.y) + ci * slot + off;\n let cumPos = 0;\n let cumNeg = 0;\n\n const segments = (cat.values ?? []).map((raw, vi) => {\n const val = toFinite(raw);\n const color = legs[vi]?.color || chartColor(vi);\n const from = val >= 0 ? cumPos : cumNeg;\n const to = from + val;\n if (val >= 0) cumPos = to;\n else cumNeg = to;\n\n const a = this.valueToPos(from);\n const b = this.valueToPos(to);\n const start = Math.min(a, b);\n const len = Math.abs(b - a);\n const rect = isV\n ? { x: along, y: start, w: thickness, h: len }\n : { x: start, y: along, w: len, h: thickness };\n return {\n ...rect,\n color,\n value: val,\n valueText: fmt(val),\n showValue: len >= MIN_SEGMENT_LABEL_PX,\n valueX: rect.x + rect.w / 2,\n valueY: rect.y + rect.h / 2,\n };\n });\n\n const total = ext[ci].total;\n const posEnd = this.valueToPos(ext[ci].pos);\n const labelPos = isV\n ? { x: p.x + (ci + 0.5) * slot, y: h - this.PB + 16 }\n : { x: p.x - 6, y: p.y + (ci + 0.5) * slot };\n // Total sits past the positive end of the stack (or at the baseline for all-negative stacks).\n const totalPos = isV\n ? { x: along + thickness / 2, y: posEnd - 6, anchor: 'middle' }\n : { x: posEnd + 6, y: along + thickness / 2, anchor: 'start' };\n\n return { label: cat.label, segments, labelPos, total, totalText: fmt(total), totalPos };\n });\n });\n\n protected readonly legendItems = computed(() => {\n const legs = this.legends();\n return legs.map((l, i) => ({\n label: l.label,\n color: l.color || chartColor(i),\n }));\n });\n\n /** Value axis along the plot edge plus the zero baseline (inside the plot when data goes negative). */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isV = this.isVertical();\n const zero = this.categories()?.length ? this.valueToPos(0) : (isV ? p.y + p.h : p.x);\n return {\n vx1: isV ? p.x : zero, vy1: p.y,\n vx2: isV ? p.x : zero, vy2: p.y + p.h,\n hx1: p.x, hy1: isV ? zero : p.y + p.h,\n hx2: p.x + p.w, hy2: isV ? zero : p.y + p.h,\n };\n });\n\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const cats = this.categories() ?? [];\n if (!cats.length) return 'Stacked bar chart: no data';\n const fmt = this.formatValue();\n const ext = this.extents();\n const seriesNames = this.legends().map((l) => l.label).filter(Boolean);\n const series = seriesNames.length ? ` (${seriesNames.join(', ')})` : '';\n const totals = cats.map((c, i) => `${c.label} ${fmt(ext[i].total)}`).join(', ');\n return `Stacked bar chart${series}: ${totals}`;\n });\n}\n","<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-stacked-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n effect,\n inject,\n input,\n isDevMode,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n ChartValueFormatter,\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface StackedBarCategory {\n label: string;\n /** One value per series. Negative values stack downward (left) from the zero baseline. */\n values: number[];\n}\n\nexport interface StackedBarLegend {\n label: string;\n color?: string;\n}\n\nexport type StackedBarOrientation = 'vertical' | 'horizontal';\n\n/**\n * Form of `lc-stacked-bar-chart` (3.36): `chart` (axes, grid, labels and\n * paddings, as before) or `band` (one horizontal bar edge to edge).\n */\nexport type StackedBarVariant = 'chart' | 'band';\n\n/** Percentages are rounded so the prerendered markup is the same everywhere. */\nconst roundPercent = (value: number): number => Math.round(value * 10000) / 10000;\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n/** Segments shorter than this along the value axis get no inline value label. */\nconst MIN_SEGMENT_LABEL_PX = 14;\n\n@Component({\n selector: 'lc-stacked-bar-chart',\n standalone: true,\n templateUrl: './stacked-bar-chart.component.html',\n styleUrls: ['./stacked-bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Stacked bar chart component for comparing category compositions.\n *\n * Features:\n * - Multiple stacked value segments per category\n * - Vertical and horizontal orientation\n * - Negative values stacked downward from a zero baseline\n * - Optional legend, grid, value labels (segments + totals), and axis labels\n * - Configurable bar gap spacing\n * - Color-coded segments with legend mapping\n * - Responsive SVG rendering\n * - `variant=\"band\"` (3.36): one horizontal distribution bar edge to edge,\n * without axes, grid or paddings\n *\n * @example\n * ```html\n * <lc-stacked-bar-chart [categories]=\"data\" [legends]=\"legends\" [showLegend]=\"true\" />\n * ```\n */\nexport class StackedBarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n if (isDevMode()) {\n effect(() => {\n const count = (this.categories() ?? []).length;\n if (this.variant() === 'band' && count !== 1) {\n console.warn(`[lc-stacked-bar-chart] variant=\"band\" needs exactly one category (got ${count}): rendering as chart`);\n }\n });\n }\n }\n\n /** Categories with stacked values. */\n categories = input.required<StackedBarCategory[]>();\n\n /** Legend items mapping to each value index. */\n legends = input<StackedBarLegend[]>([]);\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n */\n width = input<number | undefined>(undefined);\n height = input<number>(200);\n orientation = input<StackedBarOrientation>('vertical');\n showLabels = input<boolean>(true);\n /** Show the value inside each segment (when it fits) and the total at the end of each stack. */\n showValues = input<boolean>(false);\n showLegend = input<boolean>(true);\n showGrid = input<boolean>(true);\n barGap = input<number>(0.3);\n\n /** Formats segment/total value labels and axis ticks. Defaults to a float-safe `String(value)`. */\n formatValue = input<ChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary listing the\n * series names and every category's total.\n */\n ariaLabel = input<string>('');\n\n /**\n * Form of the chart (3.36, cockpit row 2). `chart`: axes, grid, labels and\n * paddings, as before. `band`: one horizontal bar that fills the host's\n * width edge to edge, `height` px thick, scaled from 0 to the sum of the\n * values, without axes, grid, labels, values or paddings; the parts are\n * split by a 2 px gap in the surface colour (CSS hooks\n * `--lc-stacked-bar-chart-band-gap`, default `2px`, and\n * `--lc-stacked-bar-chart-band-gap-color`, default `var(--color-surface)`:\n * set it to the band's background off a card). `ariaLabel`, `legends` and\n * the legend work as in `chart`. Needs exactly one category (otherwise it\n * renders as `chart`, with a warning in dev mode); `orientation` is\n * ignored; negative values draw nothing, and a sum of 0 draws no parts.\n * Part widths are percentages, so the band is exact in prerendered HTML.\n * @default 'chart'\n */\n variant = input<StackedBarVariant>('chart');\n\n /** `band` with exactly one category (3.36); otherwise the chart renders. */\n protected readonly isBand = computed(() => this.variant() === 'band' && (this.categories() ?? []).length === 1);\n\n /** The band's parts as percentages of the width: 0 … sum of the positive values. */\n protected readonly bandParts = computed(() => {\n if (!this.isBand()) return [];\n const legs = this.legends();\n const values = (this.categories()[0].values ?? []).map((raw) => Math.max(0, toFinite(raw)));\n const sum = values.reduce((a, b) => a + b, 0);\n if (sum <= 0) return [];\n let at = 0;\n return values.map((value, i) => {\n const x = (at / sum) * 100;\n at += value;\n return { x: roundPercent(x), w: roundPercent((value / sum) * 100), color: legs[i]?.color || chartColor(i) };\n });\n });\n\n /** The boundaries between two drawn parts, where the band's gap lines sit. */\n protected readonly bandGaps = computed(() => {\n const drawn = this.bandParts().filter((part) => part.w > 0);\n return drawn.slice(1).map((part) => part.x);\n });\n\n private readonly PL = 40;\n private readonly PR = 10;\n private readonly PT = 10;\n private readonly PB = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(() => `0 0 ${this.effectiveWidth()} ${this.height()}`);\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n private readonly plot = computed(() => ({\n x: this.PL,\n y: this.PT,\n w: this.effectiveWidth() - this.PL - this.PR,\n h: this.height() - this.PT - this.PB,\n }));\n\n /** Positive and negative stack extents per category (positives and negatives stack separately). */\n private readonly extents = computed(() =>\n (this.categories() ?? []).map((c) => {\n let pos = 0;\n let neg = 0;\n for (const raw of c.values ?? []) {\n const v = toFinite(raw);\n if (v >= 0) pos += v;\n else neg += v;\n }\n return { pos, neg, total: pos + neg };\n })\n );\n\n /**\n * One scale for grid *and* segments: nice tick bounds that always include\n * zero and extend below it when a stack goes negative.\n */\n protected readonly scale = computed(() => {\n const ext = this.extents();\n const max = Math.max(0, ...ext.map((e) => e.pos));\n const min = Math.min(0, ...ext.map((e) => e.neg));\n return niceScale(min, max, 4);\n });\n\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.categories()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({ pos: this.valueToPos(tick), label: fmt(tick) }));\n });\n\n protected readonly stacks = computed(() => {\n const cats = this.categories();\n const legs = this.legends();\n if (!cats?.length) return [];\n\n const p = this.plot();\n const h = this.height();\n const isV = this.isVertical();\n const gap = this.barGap();\n const fmt = this.formatValue();\n const ext = this.extents();\n\n const slot = (isV ? p.w : p.h) / cats.length;\n const thickness = slot * (1 - gap);\n const off = (slot - thickness) / 2;\n\n return cats.map((cat, ci) => {\n const along = (isV ? p.x : p.y) + ci * slot + off;\n let cumPos = 0;\n let cumNeg = 0;\n\n const segments = (cat.values ?? []).map((raw, vi) => {\n const val = toFinite(raw);\n const color = legs[vi]?.color || chartColor(vi);\n const from = val >= 0 ? cumPos : cumNeg;\n const to = from + val;\n if (val >= 0) cumPos = to;\n else cumNeg = to;\n\n const a = this.valueToPos(from);\n const b = this.valueToPos(to);\n const start = Math.min(a, b);\n const len = Math.abs(b - a);\n const rect = isV\n ? { x: along, y: start, w: thickness, h: len }\n : { x: start, y: along, w: len, h: thickness };\n return {\n ...rect,\n color,\n value: val,\n valueText: fmt(val),\n showValue: len >= MIN_SEGMENT_LABEL_PX,\n valueX: rect.x + rect.w / 2,\n valueY: rect.y + rect.h / 2,\n };\n });\n\n const total = ext[ci].total;\n const posEnd = this.valueToPos(ext[ci].pos);\n const labelPos = isV\n ? { x: p.x + (ci + 0.5) * slot, y: h - this.PB + 16 }\n : { x: p.x - 6, y: p.y + (ci + 0.5) * slot };\n // Total sits past the positive end of the stack (or at the baseline for all-negative stacks).\n const totalPos = isV\n ? { x: along + thickness / 2, y: posEnd - 6, anchor: 'middle' }\n : { x: posEnd + 6, y: along + thickness / 2, anchor: 'start' };\n\n return { label: cat.label, segments, labelPos, total, totalText: fmt(total), totalPos };\n });\n });\n\n protected readonly legendItems = computed(() => {\n const legs = this.legends();\n return legs.map((l, i) => ({\n label: l.label,\n color: l.color || chartColor(i),\n }));\n });\n\n /** Value axis along the plot edge plus the zero baseline (inside the plot when data goes negative). */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isV = this.isVertical();\n const zero = this.categories()?.length ? this.valueToPos(0) : (isV ? p.y + p.h : p.x);\n return {\n vx1: isV ? p.x : zero, vy1: p.y,\n vx2: isV ? p.x : zero, vy2: p.y + p.h,\n hx1: p.x, hy1: isV ? zero : p.y + p.h,\n hx2: p.x + p.w, hy2: isV ? zero : p.y + p.h,\n };\n });\n\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const cats = this.categories() ?? [];\n if (!cats.length) return 'Stacked bar chart: no data';\n const fmt = this.formatValue();\n const ext = this.extents();\n const seriesNames = this.legends().map((l) => l.label).filter(Boolean);\n const series = seriesNames.length ? ` (${seriesNames.join(', ')})` : '';\n const totals = cats.map((c, i) => `${c.label} ${fmt(ext[i].total)}`).join(', ');\n return `Stacked bar chart${series}: ${totals}`;\n });\n}\n","<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n"],"mappings":"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"}