@lifecorner/angular-ui-kit 3.33.1 → 3.35.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 (38) hide show
  1. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
  2. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
  3. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
  4. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +5 -2
  5. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  6. package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +21 -3
  7. package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
  8. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs +30 -2
  9. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs.map +1 -1
  10. package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +22 -3
  12. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs +6 -1
  14. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +25 -3
  16. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  17. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs +31 -2
  18. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs.map +1 -1
  19. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +41 -1
  20. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +3 -0
  22. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  23. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/src/styles/theme-contrast.spec.ts +2 -0
  26. package/types/lifecorner-angular-ui-kit-alert.d.ts +12 -2
  27. package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +5 -1
  28. package/types/lifecorner-angular-ui-kit-combobox.d.ts +21 -0
  29. package/types/lifecorner-angular-ui-kit-confirm-dialog.d.ts +16 -0
  30. package/types/lifecorner-angular-ui-kit-drawer.d.ts +37 -1
  31. package/types/lifecorner-angular-ui-kit-gallery.d.ts +3 -1
  32. package/types/lifecorner-angular-ui-kit-header.d.ts +15 -0
  33. package/types/lifecorner-angular-ui-kit-internal-overlay.d.ts +9 -1
  34. package/types/lifecorner-angular-ui-kit-list.d.ts +26 -0
  35. package/types/lifecorner-angular-ui-kit-modal.d.ts +38 -2
  36. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +42 -1
  37. package/types/lifecorner-angular-ui-kit-textarea.d.ts +17 -0
  38. package/types/lifecorner-angular-ui-kit-toast.d.ts +4 -2
@@ -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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"}
@@ -95,6 +95,9 @@ var TextareaComponent = class TextareaComponent {
95
95
  onBlur() {
96
96
  this.onTouched();
97
97
  }
98
+ focus(options) {
99
+ this.textareaElement()?.nativeElement.focus(options);
100
+ }
98
101
  writeValue(value) {
99
102
  this.value.set(value || "");
100
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n OutputRef,\n PLATFORM_ID,\n afterRenderEffect,\n computed,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n * - Plain form posts without JavaScript: `name`, and a `value` that is\n * prerendered into the native `<textarea>`\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * <lc-textarea label=\"Message\" name=\"message\" [required]=\"true\" [value]=\"draft\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /**\n * Visual variant of the textarea\n * @default 'outline'\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n * @default false\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Show the `*` after the label of a `required` field. `false` keeps the\n * required semantics (`required`, `aria-required`) without the asterisk —\n * for forms where every field is required.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /**\n * Deprecated, no effect (it never had one in `lc-textarea`). The `*` is\n * decorative and hidden from assistive technology, because the native\n * `required` state is announced already, in the screen reader's language; a\n * name on the `*` would make the field say \"required\" twice. Kept so\n * existing bindings compile.\n * @deprecated No effect: the native `required` state is announced instead.\n * @default 'required'\n */\n readonly requiredAriaLabel = input<string>('required');\n\n /**\n * Id of the native `<textarea>`. The label's `for`, the error\n * (`<id>-error`), the helper text (`<id>-helper`) and `aria-describedby`\n * derive from it, so an error summary can link to the field\n * (`<a href=\"#contact-message\">`) and prerendered HTML is the same in every\n * build. Unique in the document, no spaces. Unset (or empty) = a generated\n * `lc-textarea-<n>`, which follows creation order (on the server it keeps\n * counting across prerendered routes): set it on prerendered pages.\n * @default generated (lc-textarea-<n>)\n */\n readonly controlId = input<string | undefined>(undefined);\n\n /**\n * Announce the error message the moment it appears (`role=\"alert\"` on the\n * message). Set `false` when an `lc-alert` error summary above the form\n * already announces the errors (a page re-rendered after a failed submit,\n * e.g. 422), so they aren't read twice. The message stays visible, stays\n * linked through `aria-describedby` (read with the field), and the control\n * keeps `aria-invalid`.\n * @default true\n */\n readonly announceError = input<boolean>(true);\n\n /**\n * Whether the textarea is readonly\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n * @default ''\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n * @default ''\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n * @default ''\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n * @default undefined\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Native `name` of the inner `<textarea>`, so a form posts the text without\n * JavaScript. Unset = no attribute. A `disabled` textarea is not posted\n * (HTML); use `readonly` to post a fixed text.\n * @default undefined\n */\n readonly name = input<string>();\n\n /**\n * Number of visible text rows\n * @default 3\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n * @default undefined\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n * @default false\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n * @default 3\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n * @default undefined\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Current text, two-way bindable (`[value]` / `[(value)]`). It pre-fills the\n * native textarea, in prerendered HTML too, so a server-rendered page can\n * keep a visitor's text. Emits `valueChange` whenever it changes: once per\n * keystroke that changes the text, and when a bound form control or code\n * sets a different text. With a form control, the control's value wins;\n * don't bind both.\n * @default ''\n */\n readonly value = model<string>('');\n\n /**\n * The model's change output as a class property, so code that subscribes to\n * it (`textarea.valueChange.subscribe(…)`, `outputToObservable(…)`) keeps\n * compiling as in 3.9. Template bindings `(valueChange)` use the model.\n */\n readonly valueChange: OutputRef<string> = this.value;\n\n /** Generated id, taken once per instance (also with `controlId`), so later ids don't shift. */\n private readonly generatedId = `lc-textarea-${nextUniqueId++}`;\n\n private readonly resolvedId = computed(() => this.controlId() || this.generatedId);\n\n /** Id of the native textarea: `controlId`, or the generated `lc-textarea-<n>`. Links label, error and helper text to it. */\n get textareaId(): string {\n return this.resolvedId();\n }\n\n /** Id of the error message: `<textareaId>-error`. */\n get errorId(): string {\n return `${this.resolvedId()}-error`;\n }\n\n /** Id of the helper text: `<textareaId>-helper`. */\n get helperId(): string {\n return `${this.resolvedId()}-helper`;\n }\n\n /**\n * The native textarea's value. On the server a leading line break gets one\n * extra `\\n`: the HTML parser drops the first line break after `<textarea>`,\n * and the server's DOM serialiser doesn't add the one the HTML\n * serialisation rules require, so the prerendered page would lose it.\n */\n protected readonly renderedValue = computed(() => {\n const value = this.value();\n return this.isServer && /^[\\r\\n]/.test(value) ? `\\n${value}` : value;\n });\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n OutputRef,\n PLATFORM_ID,\n afterRenderEffect,\n computed,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n * - Plain form posts without JavaScript: `name`, and a `value` that is\n * prerendered into the native `<textarea>`\n * - `focus()` (3.35): moves the focus into the native `<textarea>` when a\n * form opens or after a rejected save (WCAG 2.4.3)\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * <lc-textarea label=\"Message\" name=\"message\" [required]=\"true\" [value]=\"draft\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /**\n * Visual variant of the textarea\n * @default 'outline'\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n * @default false\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Show the `*` after the label of a `required` field. `false` keeps the\n * required semantics (`required`, `aria-required`) without the asterisk —\n * for forms where every field is required.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /**\n * Deprecated, no effect (it never had one in `lc-textarea`). The `*` is\n * decorative and hidden from assistive technology, because the native\n * `required` state is announced already, in the screen reader's language; a\n * name on the `*` would make the field say \"required\" twice. Kept so\n * existing bindings compile.\n * @deprecated No effect: the native `required` state is announced instead.\n * @default 'required'\n */\n readonly requiredAriaLabel = input<string>('required');\n\n /**\n * Id of the native `<textarea>`. The label's `for`, the error\n * (`<id>-error`), the helper text (`<id>-helper`) and `aria-describedby`\n * derive from it, so an error summary can link to the field\n * (`<a href=\"#contact-message\">`) and prerendered HTML is the same in every\n * build. Unique in the document, no spaces. Unset (or empty) = a generated\n * `lc-textarea-<n>`, which follows creation order (on the server it keeps\n * counting across prerendered routes): set it on prerendered pages.\n * @default generated (lc-textarea-<n>)\n */\n readonly controlId = input<string | undefined>(undefined);\n\n /**\n * Announce the error message the moment it appears (`role=\"alert\"` on the\n * message). Set `false` when an `lc-alert` error summary above the form\n * already announces the errors (a page re-rendered after a failed submit,\n * e.g. 422), so they aren't read twice. The message stays visible, stays\n * linked through `aria-describedby` (read with the field), and the control\n * keeps `aria-invalid`.\n * @default true\n */\n readonly announceError = input<boolean>(true);\n\n /**\n * Whether the textarea is readonly\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n * @default ''\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n * @default ''\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n * @default ''\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n * @default undefined\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Native `name` of the inner `<textarea>`, so a form posts the text without\n * JavaScript. Unset = no attribute. A `disabled` textarea is not posted\n * (HTML); use `readonly` to post a fixed text.\n * @default undefined\n */\n readonly name = input<string>();\n\n /**\n * Number of visible text rows\n * @default 3\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n * @default undefined\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n * @default false\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n * @default 3\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n * @default undefined\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Current text, two-way bindable (`[value]` / `[(value)]`). It pre-fills the\n * native textarea, in prerendered HTML too, so a server-rendered page can\n * keep a visitor's text. Emits `valueChange` whenever it changes: once per\n * keystroke that changes the text, and when a bound form control or code\n * sets a different text. With a form control, the control's value wins;\n * don't bind both.\n * @default ''\n */\n readonly value = model<string>('');\n\n /**\n * The model's change output as a class property, so code that subscribes to\n * it (`textarea.valueChange.subscribe(…)`, `outputToObservable(…)`) keeps\n * compiling as in 3.9. Template bindings `(valueChange)` use the model.\n */\n readonly valueChange: OutputRef<string> = this.value;\n\n /** Generated id, taken once per instance (also with `controlId`), so later ids don't shift. */\n private readonly generatedId = `lc-textarea-${nextUniqueId++}`;\n\n private readonly resolvedId = computed(() => this.controlId() || this.generatedId);\n\n /** Id of the native textarea: `controlId`, or the generated `lc-textarea-<n>`. Links label, error and helper text to it. */\n get textareaId(): string {\n return this.resolvedId();\n }\n\n /** Id of the error message: `<textareaId>-error`. */\n get errorId(): string {\n return `${this.resolvedId()}-error`;\n }\n\n /** Id of the helper text: `<textareaId>-helper`. */\n get helperId(): string {\n return `${this.resolvedId()}-helper`;\n }\n\n /**\n * The native textarea's value. On the server a leading line break gets one\n * extra `\\n`: the HTML parser drops the first line break after `<textarea>`,\n * and the server's DOM serialiser doesn't add the one the HTML\n * serialisation rules require, so the prerendered page would lose it.\n */\n protected readonly renderedValue = computed(() => {\n const value = this.value();\n return this.isServer && /^[\\r\\n]/.test(value) ? `\\n${value}` : value;\n });\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n /**\n * Moves the focus into the native `<textarea>` (3.35), e.g. when a form\n * opens with this field first, or to the field with the error after a\n * rejected save (422) (WCAG 2.4.3). `options` as for `HTMLElement.focus()`\n * (`{ preventScroll: true }`). Shows the same focus ring as a user's focus.\n * A disabled field takes no focus (it stays where it is); a read-only one\n * does. Call it once the field has rendered, e.g.\n * `afterNextRender(() => this.note().focus(), { injector })` with\n * `note = viewChild.required(TextareaComponent)` and the page component's\n * `injector = inject(Injector)`. In an `lc-modal` that opens with the field,\n * that call runs after the dialog moved the focus to its first control.\n * Does nothing before the component has rendered.\n * @param options Passed to the native `focus()`.\n */\n focus(options?: FocusOptions): void {\n this.textareaElement()?.nativeElement.focus(options);\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable.\n *\n * Bottom stacks show the newest toast nearest the edge (3.33); top stacks the\n * oldest at the top. The toasts of a stack are 8 px (`--spacing-2`) apart\n * (3.33.1), within a live region and across the two; an empty region and the\n * last toast add no space, and the stack keeps its 16 px padding. A `top-*`\n * stack lies over the page on purpose (a toast stays visible wherever the\n * page is scrolled), so in the shell it can cover a title right under the\n * header (at 360 px a toast at y 72 over an h1 at y 80): prefer `bottom-*`\n * there.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n /**\n * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its\n * newest toast. That region gets `lc-toast-outlet__region--latest` and is\n * laid out at the edge, so the newest toast sits nearest the edge (and, in\n * a scrolling stack, in view at rest). DOM and announcement order stay.\n */\n protected readonly latestLive = computed(() => {\n const latest = new Map<ToastPosition, ToastLive>();\n for (const toast of this.toastService.toasts()) {\n if (toast.position.startsWith('bottom-')) {\n latest.set(toast.position, ToastOutletComponent.liveFor(toast));\n }\n }\n return latest;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable.\n *\n * Bottom stacks show the newest toast nearest the edge (3.33). A top stack\n * shows its assertive region (error, warning) above its polite one (success,\n * info), each oldest first, so a newer error sits above an older success\n * (3.35, 053-n2). The toasts of a stack are 8 px (`--spacing-2`) apart\n * (3.33.1), within a live region and across the two; an empty region and the\n * last toast add no space, and the stack keeps its 16 px padding. A `top-*`\n * stack lies over the page on purpose (a toast stays visible wherever the\n * page is scrolled), so in the shell it can cover a title right under the\n * header (at 360 px a toast at y 72 over an h1 at y 80): prefer `bottom-*`\n * there.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n /**\n * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its\n * newest toast. That region gets `lc-toast-outlet__region--latest` and is\n * laid out at the edge, so the newest toast sits nearest the edge (and, in\n * a scrolling stack, in view at rest). DOM and announcement order stay.\n */\n protected readonly latestLive = computed(() => {\n const latest = new Map<ToastPosition, ToastLive>();\n for (const toast of this.toastService.toasts()) {\n if (toast.position.startsWith('bottom-')) {\n latest.set(toast.position, ToastOutletComponent.liveFor(toast));\n }\n }\n return latest;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifecorner/angular-ui-kit",
3
- "version": "3.33.1",
3
+ "version": "3.35.0",
4
4
  "description": "LifeCorner Design System – Angular UI component library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1647,6 +1647,8 @@ describe('Component pairings of 3.25.0 (request 039)', () => {
1647
1647
  ['textarea', '.lc-textarea--error:focus', '2px solid var(--color-error-600)', '0'],
1648
1648
  ['number', '.number-input--error .number-input__control:focus-within', '2px solid var(--color-error-600)', '0'],
1649
1649
  ['combobox', '.lc-combobox--error .lc-combobox__input-wrapper:focus-within', '2px solid var(--color-error-default)', '0'],
1650
+ // 3.35 (055 D-3): the rest focus ring, so `focus()` from code shows it too.
1651
+ ['combobox', '.lc-combobox__input-wrapper:focus-within', primary, '0'],
1650
1652
  ];
1651
1653
 
1652
1654
  it.each(rings)('AC-2: %s %s draws a solid 2 px outline, no halo', (sheet, selector, outline, offset) => {
@@ -2,7 +2,10 @@ import * as i0 from "@angular/core";
2
2
  export type AlertVariant = 'success' | 'error' | 'warning' | 'info';
3
3
  /**
4
4
  * Layout of the projected actions (`slot="action"`).
5
- * - `inline`: one action right of the body, vertically centred (3.2)
5
+ * - `inline`: one action right of the body, vertically centred (3.2); from
6
+ * 16rem to 30rem inside the border (alerts of 258–481 px at 100 % text,
7
+ * 514–961 px at 200 %) a button action takes its own row below the text,
8
+ * lined up with it, at its natural width (3.35)
6
9
  * - `stacked`: the actions in their own row below the title and message;
7
10
  * below 576 px of alert width one per line at full width, 8 px apart,
8
11
  * from 576 px side by side at their natural width
@@ -25,6 +28,10 @@ export type AlertActionsLayout = 'inline' | 'stacked';
25
28
  * several actions stacked below it (`actionsLayout="stacked"`); 3.12: a text
26
29
  * link (`<a slot="action" href="…">`) as the one way forward gets a
27
30
  * 44 × 44 px target, link colour, an underline and a focus ring
31
+ * - 3.35: from 16rem to 30rem inside the border (alerts of 258–481 px at
32
+ * 100 % text, 514–961 px at 200 %) an inline button action takes its own
33
+ * row below the text, lined up with it, at its natural width, so the
34
+ * message keeps about 200 px beside the ×
28
35
  * - 3.13.1: narrow layout below 16rem inside the border (alerts under 258 px
29
36
  * at 100 % text, 514 px at 200 %): the icon shares the first row with the
30
37
  * title, message and actions take the full width below, the padding is one
@@ -84,7 +91,10 @@ export declare class AlertComponent {
84
91
  iconAriaLabel: import("@angular/core").InputSignal<string | undefined>;
85
92
  /**
86
93
  * Layout of the projected actions (`slot="action"`): `inline` — one action
87
- * right of the body (3.2); `stacked` — the actions in their own row below
94
+ * right of the body (3.2); from 16rem to 30rem inside the border (alerts of
95
+ * 258–481 px at 100 % text, 514–961 px at 200 %) a button action takes its
96
+ * own row below the text, lined up with it, at its natural width (3.35);
97
+ * `stacked` — the actions in their own row below
88
98
  * the title and message, one per line at full width and 8 px apart below
89
99
  * 576 px of alert width, side by side at their natural width from there.
90
100
  * @default 'inline'