@lifecorner/angular-ui-kit 3.11.4 → 3.11.5

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 (28) hide show
  1. package/fesm2022/lifecorner-angular-ui-kit-accordion.mjs +6 -3
  2. package/fesm2022/lifecorner-angular-ui-kit-accordion.mjs.map +1 -1
  3. package/fesm2022/lifecorner-angular-ui-kit-calendar.mjs +11 -6
  4. package/fesm2022/lifecorner-angular-ui-kit-calendar.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +7 -2
  6. package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +7 -2
  8. package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +4 -2
  10. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs +20 -0
  12. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs.map +1 -0
  13. package/fesm2022/lifecorner-angular-ui-kit-pagination.mjs +5 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-pagination.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +6 -2
  16. package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
  17. package/fesm2022/lifecorner-angular-ui-kit-stepper.mjs +11 -6
  18. package/fesm2022/lifecorner-angular-ui-kit-stepper.mjs.map +1 -1
  19. package/package.json +5 -1
  20. package/types/lifecorner-angular-ui-kit-accordion.d.ts +31 -9
  21. package/types/lifecorner-angular-ui-kit-calendar.d.ts +1 -0
  22. package/types/lifecorner-angular-ui-kit-file-upload.d.ts +18 -3
  23. package/types/lifecorner-angular-ui-kit-gallery.d.ts +17 -2
  24. package/types/lifecorner-angular-ui-kit-header.d.ts +12 -1
  25. package/types/lifecorner-angular-ui-kit-internal-a11y.d.ts +27 -0
  26. package/types/lifecorner-angular-ui-kit-pagination.d.ts +15 -2
  27. package/types/lifecorner-angular-ui-kit-rich-text-editor.d.ts +13 -1
  28. package/types/lifecorner-angular-ui-kit-stepper.d.ts +16 -5
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-header.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/header/header.component.ts","../../../../libs/angular-ui-kit/src/lib/header/header.component.html"],"sourcesContent":["import {\n Component,\n ElementRef,\n Injector,\n input,\n output,\n signal,\n computed,\n inject,\n viewChild,\n afterRenderEffect,\n ChangeDetectionStrategy,\n ViewEncapsulation,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { RouterModule } from '@angular/router';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_USER } from '@lifecorner/angular-ui-kit/icons';\nimport { AvatarColor, AvatarComponent, ɵavatarInitials } from '@lifecorner/angular-ui-kit/avatar';\nimport { MenuComponent, MenuItem } from '@lifecorner/angular-ui-kit/menu';\nimport { ThemeService } from '@lifecorner/angular-ui-kit/theme';\n\n/** A link in the header's main navigation (`navItems`). */\nexport interface HeaderNavItem {\n /** Visible link text. */\n label: string;\n /** Plain URL — rendered as a native `href`, no router involved. */\n href: string;\n /** Marks the page the visitor is on (`aria-current=\"page\"` + underline). */\n current?: boolean;\n}\n\n/** Element used for the header title (`titleTag`). */\nexport type HeaderTitleTag = 'h1' | 'h2' | 'h3' | 'p' | 'span' | 'div';\n\n/**\n * Surface of the header: `default` is the bar on `--color-surface` with a\n * bottom border; `transparent` has no background and no border, for a header\n * that sits on the page (\"Weite\").\n */\nexport type HeaderVariant = 'default' | 'transparent';\n\n/**\n * What the account-menu button shows: `icon` (a user icon and a chevron) or\n * `avatar` (the person's initials avatar, `userName`, `userAvatarSrc`,\n * `userAvatarColor`, in a 48 px round button).\n */\nexport type HeaderUserMenuTrigger = 'icon' | 'avatar';\n\n/**\n * AppHeaderComponent - Global application header for the LifeCorner shell\n *\n * Features:\n * - Clickable logo for home navigation\n * - Optional title and subtitle display\n * - User profile dropdown with avatar, name, email, optional Profile link, and Logout\n * - Optional theme toggle button in header. By default the button toggles the\n * `ThemeService` itself and additionally emits `themeToggleClick`, so the\n * handler must NOT toggle again (that would undo the switch). To own the\n * toggling in the app, set `[autoToggleTheme]=\"false\"` and toggle in the\n * handler.\n * - Hamburger menu toggle for mobile sidebar; bind `sidenavOpen` (and\n * `sidenavId`) so the toggle exposes `aria-expanded` / `aria-controls`.\n * - OnPush change detection for performance\n *\n * Public, prerendered sites (no router, no JavaScript needed):\n * - `homeHref` makes the brand a plain link (instead of `routerLink=\"/\"`).\n * - `navItems` render a labelled main navigation (`<nav aria-label><ul>` with\n * `aria-current=\"page\"` on the current item); below 1024 px it moves to a\n * second row and wraps.\n * - `[slot='end']` takes content at the end of the first row (e.g. a language\n * link), `showUserMenu` hides the account menu, `titleTag` picks the title\n * element (the page keeps its own `<h1>`), `sticky` can be switched off.\n * - The end content (slot, theme button, context, account menu) is one group:\n * when it doesn't fit beside the logo and the title's longest word (200 %\n * text on a phone), it takes the end of a second row instead of covering the\n * brand or widening the page (3.10.1, AS-31). The subtitle ends with \"…\".\n * App shell (\"Weite\", kit 3.3):\n * - `variant=\"transparent\"`: no background and no border, the header sits on\n * the page (pair it with `[sticky]=\"false\"`, a header without a surface\n * cannot stay over scrolling text).\n * - `userMenuTrigger=\"avatar\"`: the account button is the person's initials\n * avatar (`userAvatarColor` for the member colour, `userAvatarSrc` for a\n * photo) in a 48 px round button; the icon trigger is 44 px high in every\n * density.\n * - The subtitle (e.g. a household name) stays on one line and ends with \"…\";\n * the full text is its `title` and stays in the accessible text. It is shown\n * as typed (no uppercase).\n * - The account menu is at most `min(18rem, 100vw − 2rem)` wide; the name and\n * email in its header wrap instead of being cut.\n * - Every built-in label is an input (`homeAriaLabel`, `hamburgerAriaLabel`,\n * `userMenuAriaLabel`, `navAriaLabel`, `profileLabel`, `logoutLabel`,\n * `themeToDarkAriaLabel`, `themeToLightAriaLabel`, `contextAriaLabel`);\n * the defaults stay English.\n * - The `ThemeService` (which persists to localStorage) is only created when\n * the theme button is shown.\n *\n * @example\n * ```html\n * <lc-header\n * [logo]=\"'/assets/logo.svg'\"\n * [title]=\"'LifeCorner'\"\n * [subtitle]=\"'User Profile'\"\n * [userName]=\"'John Doe'\"\n * [userEmail]=\"'user@example.com'\"\n * [showHamburger]=\"true\"\n * [sidenavOpen]=\"sidenavOpen\"\n * sidenavId=\"app-sidenav\"\n * [showThemeButton]=\"true\"\n * [showProfileMenuItem]=\"true\"\n * (hamburgerClick)=\"toggleSidebar()\"\n * (themeToggleClick)=\"trackThemeToggle()\"\n * (profileClick)=\"navigateToProfile()\"\n * (logoutClick)=\"handleLogout()\"\n * />\n *\n * <!-- App-controlled theme: the header only reports the click -->\n * <lc-header\n * [showThemeButton]=\"true\"\n * [autoToggleTheme]=\"false\"\n * (themeToggleClick)=\"toggleTheme()\"\n * />\n *\n * <!-- Public site: plain links, labelled nav, language link at the end -->\n * <lc-header\n * size=\"sm\"\n * [sticky]=\"false\"\n * [showUserMenu]=\"false\"\n * homeHref=\"/de/\"\n * homeAriaLabel=\"Produkt – Startseite\"\n * navAriaLabel=\"Hauptnavigation\"\n * [navItems]=\"[\n * { label: 'Sicherheit', href: '/de/sicherheit', current: true },\n * { label: 'Roadmap', href: '/de/roadmap' }\n * ]\"\n * >\n * <a slot=\"end\" lcButton variant=\"ghost\" href=\"/en/security\" hreflang=\"en\" lang=\"en\">English</a>\n * </lc-header>\n * ```\n */\n@Component({\n selector: 'lc-header',\n standalone: true,\n imports: [\n NgTemplateOutlet,\n RouterModule,\n ButtonComponent,\n LogoComponent,\n IconComponent,\n AvatarComponent,\n MenuComponent,\n ],\n // Its own glyphs, and the profile menu's (lc-menu renders them in this view).\n viewProviders: [ɵprovideKitIcons([ICON_BARS_3, ICON_SUN, ICON_MOON, ICON_USER, ICON_CHEVRON_DOWN, ICON_LOGOUT])],\n templateUrl: './header.component.html',\n styleUrl: './header.component.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-header-host',\n },\n})\nexport class HeaderComponent {\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n readonly logo = input('');\n /**\n * Render the brand block. On its own this shows the emblem; with `logo` set\n * it shows the full lockup. (It used to additionally require `logo` or\n * `title`, so `showLogo` alone rendered an empty 0px-wide brand area with no\n * error — an app could end up with no branding at all and no clue why.)\n */\n readonly showLogo = input(true);\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 (forwarded to inner `<lc-logo>`). */\n readonly logoAlt = input('');\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md`. */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n /**\n * Overall header height. Useful to align the header with a sidebar brand block.\n * Maps to the same `min-height` scale the sidenav logo area uses:\n * `sm` 56px · `md` 64px (default) · `lg` 80px · `xl` 112px.\n */\n readonly size = input<'sm' | 'md' | 'lg' | 'xl'>('md');\n readonly title = input('');\n /**\n * Surface: `default` (the bar on `--color-surface` with a bottom border) or\n * `transparent` (no background, no border — the header sits on the page).\n */\n readonly variant = input<HeaderVariant>('default');\n /**\n * Element for the title. `h1` keeps the app-shell behaviour; on a site whose\n * pages carry their own `<h1>`, use `span` (or `p`/`div`).\n */\n readonly titleTag = input<HeaderTitleTag>('h1');\n /**\n * Second line under the title (e.g. the household name). One line, cut\n * with \"…\" when it does not fit; the full text is its `title` attribute.\n * Shown as typed.\n */\n readonly subtitle = input('');\n readonly userEmail = input('');\n readonly userName = input('');\n /**\n * What the account-menu button shows: `icon` (user icon and chevron) or\n * `avatar` (the initials of `userName` in a 48 px round button; the button\n * is named `userMenuAriaLabel`, a comma and the initials).\n * @default 'icon'\n */\n readonly userMenuTrigger = input<HeaderUserMenuTrigger>('icon');\n /** Photo for the account avatar (trigger and menu header); initials when empty or broken. */\n readonly userAvatarSrc = input('');\n /** Colour of the account avatar's initials: `default` or a member colour `member-1` … `member-6`. */\n readonly userAvatarColor = input<AvatarColor>('default');\n readonly showHamburger = input(false);\n /**\n * Open state of the sidenav the hamburger controls. When bound, the toggle\n * announces it via `aria-expanded`; leave `undefined` when the header does\n * not know (the attribute is then omitted rather than lying).\n */\n readonly sidenavOpen = input<boolean | undefined>(undefined);\n /** DOM id of the sidenav the hamburger controls (`aria-controls`). */\n readonly sidenavId = input('');\n readonly showThemeButton = input(false);\n /**\n * Whether the theme button toggles the `ThemeService` itself before emitting\n * `themeToggleClick`. Set to `false` when the app owns the theme switch and\n * toggles in the `(themeToggleClick)` handler — otherwise both would toggle\n * and the click would be a no-op.\n */\n readonly autoToggleTheme = input(true);\n readonly contextName = input('');\n readonly contextLabel = input('');\n readonly menuSize = input<'sm' | 'md' | 'lg'>('sm');\n readonly showProfileMenuItem = input(true);\n /** Render the account menu (avatar trigger with Profile/Logout). */\n readonly showUserMenu = input(true);\n /**\n * Plain URL for the brand link. When set, the brand is a native\n * `<a href>` — no router needed (static or prerendered sites). Empty keeps\n * `routerLink=\"/\"`.\n */\n readonly homeHref = input('');\n /**\n * Main navigation links, rendered as `<nav><ul><li><a href>` with\n * `aria-current=\"page\"` on the current one. On screens below 1024 px the\n * navigation takes a second row and wraps.\n */\n readonly navItems = input<readonly HeaderNavItem[]>([]);\n /** Keep the header at the top while scrolling (`position: sticky`). */\n readonly sticky = input(true);\n\n // Labels (defaults are English; translate them in the app).\n /** Accessible name of the brand link. */\n readonly homeAriaLabel = input('Go to home');\n /** Accessible name of the hamburger button. */\n readonly hamburgerAriaLabel = input('Toggle sidebar menu');\n /**\n * Accessible name of the account menu trigger. With `userMenuTrigger=\"avatar\"`\n * the button is named this text, a comma and the initials it shows\n * (`Menü für Johanna Müller, JM`), so a voice-control user can say what they\n * see (WCAG 2.5.3). Nothing is added when `userName` gives no initials, or\n * when the text already contains them as a word. The name is the same with a\n * photo.\n * @default 'Open user menu'\n */\n readonly userMenuAriaLabel = input('Open user menu');\n /** Accessible name of the `<nav>` around `navItems`. */\n readonly navAriaLabel = input('Main navigation');\n /** Label of the theme button while the light theme is active. */\n readonly themeToDarkAriaLabel = input('Switch to dark mode');\n /** Label of the theme button while the dark theme is active. */\n readonly themeToLightAriaLabel = input('Switch to light mode');\n /** Accessible name of the context button (`contextName`). */\n readonly contextAriaLabel = input('Context info');\n /** Label of the Profile entry in the account menu. */\n readonly profileLabel = input('Profile');\n /** Label of the Logout entry in the account menu. */\n readonly logoutLabel = input('Logout');\n\n readonly hamburgerClick = output<void>();\n readonly themeToggleClick = output<void>();\n readonly logoutClick = output<void>();\n readonly profileClick = output<void>();\n readonly contextClick = output<void>();\n\n private readonly injector = inject(Injector);\n private themeServiceInstance: ThemeService | null = null;\n\n /**\n * Resolved on first use, i.e. only when the theme button is shown: creating\n * the ThemeService reads and writes the theme class and localStorage, which a\n * site without a theme switch must not do.\n */\n protected get themeService(): ThemeService {\n return (this.themeServiceInstance ??= this.injector.get(ThemeService));\n }\n\n private readonly hamburger = viewChild('hamburger', { read: ElementRef<HTMLElement> });\n\n constructor() {\n // The hamburger is an <lc-button>; the ARIA state belongs on its native\n // <button> (the element that has the button role), not on the custom\n // element host — so it is reflected after render rather than bound in the\n // template.\n afterRenderEffect(() => {\n const button = this.hamburger()?.nativeElement.querySelector('button');\n if (!button) return;\n const open = this.sidenavOpen();\n const controls = this.sidenavId();\n if (open === undefined) {\n button.removeAttribute('aria-expanded');\n } else {\n button.setAttribute('aria-expanded', String(open));\n }\n if (controls) {\n button.setAttribute('aria-controls', controls);\n } else {\n button.removeAttribute('aria-controls');\n }\n });\n }\n\n /**\n * Name of the avatar trigger (AS-33, WCAG 2.5.3 Label in Name):\n * `userMenuAriaLabel`, a comma and the initials the avatar shows, so the\n * visible text is a word of the name. Built from the same function as the\n * avatar's initials and independent of the photo, so the prerendered name\n * and the page's agree. Nothing is added for the `?` placeholder or when\n * the label already holds the initials as a word (parentheticals dropped,\n * as axe-core 4.14 does); an empty label becomes the initials alone.\n */\n protected readonly avatarTriggerAriaLabel = computed(() => {\n const initials = ɵavatarInitials(this.userName());\n const label = this.userMenuAriaLabel();\n if (!/[\\p{L}\\p{N}]/u.test(initials)) return label;\n // toLowerCase, not toLocaleLowerCase: server and browser must agree.\n const words = label\n .replace(/\\([^()]*\\)/g, ' ')\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u);\n if (words.includes(initials.toLowerCase())) return label;\n if (label.trim() === '') return initials;\n return `${label}, ${initials}`;\n });\n\n /**\n * Get menu items for profile dropdown\n */\n protected readonly menuItems = computed<MenuItem[]>(() => {\n const items: MenuItem[] = [];\n\n // Add profile link if enabled\n if (this.showProfileMenuItem()) {\n items.push({\n id: 'profile',\n label: this.profileLabel(),\n icon: 'user',\n dividerAfter: true,\n });\n }\n\n // Add logout\n items.push({\n id: 'logout',\n label: this.logoutLabel(),\n icon: 'arrow-right-start-on-rectangle',\n variant: 'danger',\n });\n\n return items;\n });\n\n /**\n * Dropdown open state (using Angular signals)\n */\n private readonly dropdownOpen = signal<boolean>(false);\n\n /**\n * Get dropdown open state\n */\n isDropdownOpen(): boolean {\n return this.dropdownOpen();\n }\n\n /**\n * Toggle profile dropdown visibility\n */\n toggleDropdown(): void {\n this.dropdownOpen.set(!this.dropdownOpen());\n }\n\n /**\n * Close profile dropdown\n */\n closeDropdown(): void {\n this.dropdownOpen.set(false);\n }\n\n /**\n * Handle hamburger click\n */\n onHamburgerClick(): void {\n this.hamburgerClick.emit();\n }\n\n /**\n * Handle theme button click (separate button, not in menu). Toggles the\n * theme unless the app opted out via `autoToggleTheme`, then emits.\n */\n onThemeButtonClick(): void {\n if (this.autoToggleTheme()) {\n this.themeService.toggleTheme();\n }\n this.themeToggleClick.emit();\n }\n\n /**\n * Handle logout click\n */\n onLogoutClick(): void {\n this.logoutClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle profile click\n */\n onProfileClick(): void {\n this.profileClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle menu item click\n */\n onMenuItemClick(item: MenuItem): void {\n if (item.id === 'logout') {\n this.onLogoutClick();\n } else if (item.id === 'profile') {\n this.onProfileClick();\n }\n }\n}\n","<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.title]=\"contextName()\"\n [attr.aria-label]=\"contextAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>\n }\n <span class=\"lc-header__context-name\">{{ contextName() }}</span>\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"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() || title() || 'Logo'\"\n />\n</ng-template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-header.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/header/header.component.ts","../../../../libs/angular-ui-kit/src/lib/header/header.component.html"],"sourcesContent":["import {\n Component,\n ElementRef,\n Injector,\n input,\n output,\n signal,\n computed,\n inject,\n viewChild,\n afterRenderEffect,\n ChangeDetectionStrategy,\n ViewEncapsulation,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { RouterModule } from '@angular/router';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { LogoComponent } from '@lifecorner/angular-ui-kit/logo';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_BARS_3, ICON_CHEVRON_DOWN, ICON_LOGOUT, ICON_MOON, ICON_SUN, ICON_USER } from '@lifecorner/angular-ui-kit/icons';\nimport { AvatarColor, AvatarComponent, ɵavatarInitials } from '@lifecorner/angular-ui-kit/avatar';\nimport { MenuComponent, MenuItem } from '@lifecorner/angular-ui-kit/menu';\nimport { ThemeService } from '@lifecorner/angular-ui-kit/theme';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\n/** A link in the header's main navigation (`navItems`). */\nexport interface HeaderNavItem {\n /** Visible link text. */\n label: string;\n /** Plain URL — rendered as a native `href`, no router involved. */\n href: string;\n /** Marks the page the visitor is on (`aria-current=\"page\"` + underline). */\n current?: boolean;\n}\n\n/** Element used for the header title (`titleTag`). */\nexport type HeaderTitleTag = 'h1' | 'h2' | 'h3' | 'p' | 'span' | 'div';\n\n/**\n * Surface of the header: `default` is the bar on `--color-surface` with a\n * bottom border; `transparent` has no background and no border, for a header\n * that sits on the page (\"Weite\").\n */\nexport type HeaderVariant = 'default' | 'transparent';\n\n/**\n * What the account-menu button shows: `icon` (a user icon and a chevron) or\n * `avatar` (the person's initials avatar, `userName`, `userAvatarSrc`,\n * `userAvatarColor`, in a 48 px round button).\n */\nexport type HeaderUserMenuTrigger = 'icon' | 'avatar';\n\n/**\n * AppHeaderComponent - Global application header for the LifeCorner shell\n *\n * Features:\n * - Clickable logo for home navigation\n * - Optional title and subtitle display\n * - User profile dropdown with avatar, name, email, optional Profile link, and Logout\n * - Optional theme toggle button in header. By default the button toggles the\n * `ThemeService` itself and additionally emits `themeToggleClick`, so the\n * handler must NOT toggle again (that would undo the switch). To own the\n * toggling in the app, set `[autoToggleTheme]=\"false\"` and toggle in the\n * handler.\n * - Hamburger menu toggle for mobile sidebar; bind `sidenavOpen` (and\n * `sidenavId`) so the toggle exposes `aria-expanded` / `aria-controls`.\n * - OnPush change detection for performance\n *\n * Public, prerendered sites (no router, no JavaScript needed):\n * - `homeHref` makes the brand a plain link (instead of `routerLink=\"/\"`).\n * - `navItems` render a labelled main navigation (`<nav aria-label><ul>` with\n * `aria-current=\"page\"` on the current item); below 1024 px it moves to a\n * second row and wraps.\n * - `[slot='end']` takes content at the end of the first row (e.g. a language\n * link), `showUserMenu` hides the account menu, `titleTag` picks the title\n * element (the page keeps its own `<h1>`), `sticky` can be switched off.\n * - The end content (slot, theme button, context, account menu) is one group:\n * when it doesn't fit beside the logo and the title's longest word (200 %\n * text on a phone), it takes the end of a second row instead of covering the\n * brand or widening the page (3.10.1, AS-31). The subtitle ends with \"…\".\n * App shell (\"Weite\", kit 3.3):\n * - `variant=\"transparent\"`: no background and no border, the header sits on\n * the page (pair it with `[sticky]=\"false\"`, a header without a surface\n * cannot stay over scrolling text).\n * - `userMenuTrigger=\"avatar\"`: the account button is the person's initials\n * avatar (`userAvatarColor` for the member colour, `userAvatarSrc` for a\n * photo) in a 48 px round button; the icon trigger is 44 px high in every\n * density.\n * - The subtitle (e.g. a household name) stays on one line and ends with \"…\";\n * the full text is its `title` and stays in the accessible text. It is shown\n * as typed (no uppercase).\n * - The account menu is at most `min(18rem, 100vw − 2rem)` wide; the name and\n * email in its header wrap instead of being cut.\n * - Every built-in label is an input (`homeAriaLabel`, `hamburgerAriaLabel`,\n * `userMenuAriaLabel`, `navAriaLabel`, `profileLabel`, `logoutLabel`,\n * `themeToDarkAriaLabel`, `themeToLightAriaLabel`, `contextAriaLabel`);\n * the defaults stay English.\n * - The `ThemeService` (which persists to localStorage) is only created when\n * the theme button is shown.\n *\n * @example\n * ```html\n * <lc-header\n * [logo]=\"'/assets/logo.svg'\"\n * [title]=\"'LifeCorner'\"\n * [subtitle]=\"'User Profile'\"\n * [userName]=\"'John Doe'\"\n * [userEmail]=\"'user@example.com'\"\n * [showHamburger]=\"true\"\n * [sidenavOpen]=\"sidenavOpen\"\n * sidenavId=\"app-sidenav\"\n * [showThemeButton]=\"true\"\n * [showProfileMenuItem]=\"true\"\n * (hamburgerClick)=\"toggleSidebar()\"\n * (themeToggleClick)=\"trackThemeToggle()\"\n * (profileClick)=\"navigateToProfile()\"\n * (logoutClick)=\"handleLogout()\"\n * />\n *\n * <!-- App-controlled theme: the header only reports the click -->\n * <lc-header\n * [showThemeButton]=\"true\"\n * [autoToggleTheme]=\"false\"\n * (themeToggleClick)=\"toggleTheme()\"\n * />\n *\n * <!-- Public site: plain links, labelled nav, language link at the end -->\n * <lc-header\n * size=\"sm\"\n * [sticky]=\"false\"\n * [showUserMenu]=\"false\"\n * homeHref=\"/de/\"\n * homeAriaLabel=\"Produkt – Startseite\"\n * navAriaLabel=\"Hauptnavigation\"\n * [navItems]=\"[\n * { label: 'Sicherheit', href: '/de/sicherheit', current: true },\n * { label: 'Roadmap', href: '/de/roadmap' }\n * ]\"\n * >\n * <a slot=\"end\" lcButton variant=\"ghost\" href=\"/en/security\" hreflang=\"en\" lang=\"en\">English</a>\n * </lc-header>\n * ```\n */\n@Component({\n selector: 'lc-header',\n standalone: true,\n imports: [\n NgTemplateOutlet,\n RouterModule,\n ButtonComponent,\n LogoComponent,\n IconComponent,\n AvatarComponent,\n MenuComponent,\n ],\n // Its own glyphs, and the profile menu's (lc-menu renders them in this view).\n viewProviders: [ɵprovideKitIcons([ICON_BARS_3, ICON_SUN, ICON_MOON, ICON_USER, ICON_CHEVRON_DOWN, ICON_LOGOUT])],\n templateUrl: './header.component.html',\n styleUrl: './header.component.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-header-host',\n },\n})\nexport class HeaderComponent {\n readonly theme = input<'light' | 'dark' | 'auto'>('auto');\n readonly logo = input('');\n /**\n * Render the brand block. On its own this shows the emblem; with `logo` set\n * it shows the full lockup. (It used to additionally require `logo` or\n * `title`, so `showLogo` alone rendered an empty 0px-wide brand area with no\n * error — an app could end up with no branding at all and no clue why.)\n */\n readonly showLogo = input(true);\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 (forwarded to inner `<lc-logo>`). */\n readonly logoAlt = input('');\n /** Size of the brand logo (forwarded to inner `<lc-logo>`). Defaults to `md`. */\n readonly logoSize = input<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');\n /**\n * Overall header height. Useful to align the header with a sidebar brand block.\n * Maps to the same `min-height` scale the sidenav logo area uses:\n * `sm` 56px · `md` 64px (default) · `lg` 80px · `xl` 112px.\n */\n readonly size = input<'sm' | 'md' | 'lg' | 'xl'>('md');\n readonly title = input('');\n /**\n * Surface: `default` (the bar on `--color-surface` with a bottom border) or\n * `transparent` (no background, no border — the header sits on the page).\n */\n readonly variant = input<HeaderVariant>('default');\n /**\n * Element for the title. `h1` keeps the app-shell behaviour; on a site whose\n * pages carry their own `<h1>`, use `span` (or `p`/`div`).\n */\n readonly titleTag = input<HeaderTitleTag>('h1');\n /**\n * Second line under the title (e.g. the household name). One line, cut\n * with \"…\" when it does not fit; the full text is its `title` attribute.\n * Shown as typed.\n */\n readonly subtitle = input('');\n readonly userEmail = input('');\n readonly userName = input('');\n /**\n * What the account-menu button shows: `icon` (user icon and chevron) or\n * `avatar` (the initials of `userName` in a 48 px round button; the button\n * is named `userMenuAriaLabel`, a comma and the initials).\n * @default 'icon'\n */\n readonly userMenuTrigger = input<HeaderUserMenuTrigger>('icon');\n /** Photo for the account avatar (trigger and menu header); initials when empty or broken. */\n readonly userAvatarSrc = input('');\n /** Colour of the account avatar's initials: `default` or a member colour `member-1` … `member-6`. */\n readonly userAvatarColor = input<AvatarColor>('default');\n readonly showHamburger = input(false);\n /**\n * Open state of the sidenav the hamburger controls. When bound, the toggle\n * announces it via `aria-expanded`; leave `undefined` when the header does\n * not know (the attribute is then omitted rather than lying).\n */\n readonly sidenavOpen = input<boolean | undefined>(undefined);\n /** DOM id of the sidenav the hamburger controls (`aria-controls`). */\n readonly sidenavId = input('');\n readonly showThemeButton = input(false);\n /**\n * Whether the theme button toggles the `ThemeService` itself before emitting\n * `themeToggleClick`. Set to `false` when the app owns the theme switch and\n * toggles in the `(themeToggleClick)` handler — otherwise both would toggle\n * and the click would be a no-op.\n */\n readonly autoToggleTheme = input(true);\n readonly contextName = input('');\n readonly contextLabel = input('');\n readonly menuSize = input<'sm' | 'md' | 'lg'>('sm');\n readonly showProfileMenuItem = input(true);\n /** Render the account menu (avatar trigger with Profile/Logout). */\n readonly showUserMenu = input(true);\n /**\n * Plain URL for the brand link. When set, the brand is a native\n * `<a href>` — no router needed (static or prerendered sites). Empty keeps\n * `routerLink=\"/\"`.\n */\n readonly homeHref = input('');\n /**\n * Main navigation links, rendered as `<nav><ul><li><a href>` with\n * `aria-current=\"page\"` on the current one. On screens below 1024 px the\n * navigation takes a second row and wraps.\n */\n readonly navItems = input<readonly HeaderNavItem[]>([]);\n /** Keep the header at the top while scrolling (`position: sticky`). */\n readonly sticky = input(true);\n\n // Labels (defaults are English; translate them in the app).\n /** Accessible name of the brand link. */\n readonly homeAriaLabel = input('Go to home');\n /** Accessible name of the hamburger button. */\n readonly hamburgerAriaLabel = input('Toggle sidebar menu');\n /**\n * Accessible name of the account menu trigger. With `userMenuTrigger=\"avatar\"`\n * the button is named this text, a comma and the initials it shows\n * (`Menü für Johanna Müller, JM`), so a voice-control user can say what they\n * see (WCAG 2.5.3). Nothing is added when `userName` gives no initials, or\n * when the text already contains them as a word. The name is the same with a\n * photo.\n * @default 'Open user menu'\n */\n readonly userMenuAriaLabel = input('Open user menu');\n /** Accessible name of the `<nav>` around `navItems`. */\n readonly navAriaLabel = input('Main navigation');\n /** Label of the theme button while the light theme is active. */\n readonly themeToDarkAriaLabel = input('Switch to dark mode');\n /** Label of the theme button while the dark theme is active. */\n readonly themeToLightAriaLabel = input('Switch to light mode');\n /**\n * Start of the context button's accessible name. The visible `contextLabel`\n * and `contextName` follow it (WCAG 2.5.3 Label in Name), unless the label\n * already contains them: `Context info, Tenant Acme Corp`.\n * @default 'Context info'\n */\n readonly contextAriaLabel = input('Context info');\n /** Label of the Profile entry in the account menu. */\n readonly profileLabel = input('Profile');\n /** Label of the Logout entry in the account menu. */\n readonly logoutLabel = input('Logout');\n\n readonly hamburgerClick = output<void>();\n readonly themeToggleClick = output<void>();\n readonly logoutClick = output<void>();\n readonly profileClick = output<void>();\n readonly contextClick = output<void>();\n\n private readonly injector = inject(Injector);\n private themeServiceInstance: ThemeService | null = null;\n\n /**\n * Resolved on first use, i.e. only when the theme button is shown: creating\n * the ThemeService reads and writes the theme class and localStorage, which a\n * site without a theme switch must not do.\n */\n protected get themeService(): ThemeService {\n return (this.themeServiceInstance ??= this.injector.get(ThemeService));\n }\n\n private readonly hamburger = viewChild('hamburger', { read: ElementRef<HTMLElement> });\n\n constructor() {\n // The hamburger is an <lc-button>; the ARIA state belongs on its native\n // <button> (the element that has the button role), not on the custom\n // element host — so it is reflected after render rather than bound in the\n // template.\n afterRenderEffect(() => {\n const button = this.hamburger()?.nativeElement.querySelector('button');\n if (!button) return;\n const open = this.sidenavOpen();\n const controls = this.sidenavId();\n if (open === undefined) {\n button.removeAttribute('aria-expanded');\n } else {\n button.setAttribute('aria-expanded', String(open));\n }\n if (controls) {\n button.setAttribute('aria-controls', controls);\n } else {\n button.removeAttribute('aria-controls');\n }\n });\n }\n\n /**\n * Name of the avatar trigger (AS-33, WCAG 2.5.3 Label in Name):\n * `userMenuAriaLabel`, a comma and the initials the avatar shows, so the\n * visible text is a word of the name. Built from the same function as the\n * avatar's initials and independent of the photo, so the prerendered name\n * and the page's agree. Nothing is added for the `?` placeholder or when\n * the label already holds the initials as a word (parentheticals dropped,\n * as axe-core 4.14 does); an empty label becomes the initials alone.\n */\n protected readonly avatarTriggerAriaLabel = computed(() => {\n const initials = ɵavatarInitials(this.userName());\n const label = this.userMenuAriaLabel();\n if (!/[\\p{L}\\p{N}]/u.test(initials)) return label;\n // toLowerCase, not toLocaleLowerCase: server and browser must agree.\n const words = label\n .replace(/\\([^()]*\\)/g, ' ')\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u);\n if (words.includes(initials.toLowerCase())) return label;\n if (label.trim() === '') return initials;\n return `${label}, ${initials}`;\n });\n\n /**\n * Name of the context button (AS-33, WCAG 2.5.3 Label in Name):\n * `contextAriaLabel`, a comma and the visible `contextLabel` and\n * `contextName`, so a voice-control user can say what they see.\n */\n protected readonly contextButtonAriaLabel = computed(() =>\n nameWithText(this.contextAriaLabel(), [this.contextLabel(), this.contextName()].filter(Boolean).join(' ')),\n );\n\n /**\n * Get menu items for profile dropdown\n */\n protected readonly menuItems = computed<MenuItem[]>(() => {\n const items: MenuItem[] = [];\n\n // Add profile link if enabled\n if (this.showProfileMenuItem()) {\n items.push({\n id: 'profile',\n label: this.profileLabel(),\n icon: 'user',\n dividerAfter: true,\n });\n }\n\n // Add logout\n items.push({\n id: 'logout',\n label: this.logoutLabel(),\n icon: 'arrow-right-start-on-rectangle',\n variant: 'danger',\n });\n\n return items;\n });\n\n /**\n * Dropdown open state (using Angular signals)\n */\n private readonly dropdownOpen = signal<boolean>(false);\n\n /**\n * Get dropdown open state\n */\n isDropdownOpen(): boolean {\n return this.dropdownOpen();\n }\n\n /**\n * Toggle profile dropdown visibility\n */\n toggleDropdown(): void {\n this.dropdownOpen.set(!this.dropdownOpen());\n }\n\n /**\n * Close profile dropdown\n */\n closeDropdown(): void {\n this.dropdownOpen.set(false);\n }\n\n /**\n * Handle hamburger click\n */\n onHamburgerClick(): void {\n this.hamburgerClick.emit();\n }\n\n /**\n * Handle theme button click (separate button, not in menu). Toggles the\n * theme unless the app opted out via `autoToggleTheme`, then emits.\n */\n onThemeButtonClick(): void {\n if (this.autoToggleTheme()) {\n this.themeService.toggleTheme();\n }\n this.themeToggleClick.emit();\n }\n\n /**\n * Handle logout click\n */\n onLogoutClick(): void {\n this.logoutClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle profile click\n */\n onProfileClick(): void {\n this.profileClick.emit();\n this.closeDropdown();\n }\n\n /**\n * Handle menu item click\n */\n onMenuItemClick(item: MenuItem): void {\n if (item.id === 'logout') {\n this.onLogoutClick();\n } else if (item.id === 'profile') {\n this.onProfileClick();\n }\n }\n}\n","<header\n class=\"lc-header\"\n [class.lc-header--dark]=\"theme() === 'dark'\"\n [class.lc-header--light]=\"theme() === 'light'\"\n [class.lc-header--size-sm]=\"size() === 'sm'\"\n [class.lc-header--size-md]=\"size() === 'md'\"\n [class.lc-header--size-lg]=\"size() === 'lg'\"\n [class.lc-header--size-xl]=\"size() === 'xl'\"\n [class.lc-header--has-nav]=\"navItems().length > 0\"\n [class.lc-header--static]=\"!sticky()\"\n [class.lc-header--transparent]=\"variant() === 'transparent'\"\n>\n <!-- Hamburger menu icon (mobile only) -->\n @if (showHamburger()) {\n <lc-button\n #hamburger\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"hamburgerAriaLabel()\"\n (clicked)=\"onHamburgerClick()\"\n class=\"lc-header__hamburger\"\n >\n <lc-icon name=\"bars-3\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Logo (clickable, navigates to home) -->\n <div class=\"lc-header__brand\">\n @if (showLogo()) {\n @if (homeHref()) {\n <!-- Plain link: works without the router and without JavaScript. -->\n <a [href]=\"homeHref()\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n } @else {\n <a routerLink=\"/\" class=\"lc-header__logo\" [attr.aria-label]=\"homeAriaLabel()\">\n <ng-container [ngTemplateOutlet]=\"brandLogo\" />\n </a>\n }\n }\n\n @if (title()) {\n <div class=\"lc-header__title-group\">\n @switch (titleTag()) {\n @case ('h1') {\n <h1 class=\"lc-header__title\">{{ title() }}</h1>\n }\n @case ('h2') {\n <h2 class=\"lc-header__title\">{{ title() }}</h2>\n }\n @case ('h3') {\n <h3 class=\"lc-header__title\">{{ title() }}</h3>\n }\n @case ('p') {\n <p class=\"lc-header__title\">{{ title() }}</p>\n }\n @case ('div') {\n <div class=\"lc-header__title\">{{ title() }}</div>\n }\n @default {\n <span class=\"lc-header__title\">{{ title() }}</span>\n }\n }\n @if (subtitle()) {\n <span class=\"lc-header__subtitle\" [attr.title]=\"subtitle()\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Main navigation: inline from 1024 px, its own wrapping row below. -->\n @if (navItems().length > 0) {\n <nav class=\"lc-header__nav\" [attr.aria-label]=\"navAriaLabel()\">\n <ul class=\"lc-header__nav-list\">\n @for (item of navItems(); track $index) {\n <li class=\"lc-header__nav-item\">\n <a\n class=\"lc-header__nav-link\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.current ? 'page' : null\"\n >{{ item.label }}</a>\n </li>\n }\n </ul>\n </nav>\n }\n\n <!-- Zero-width spacer on the brand's row: keeps 3.10's two gaps before the end\n group and the first row's height; grows in a has-nav header from 1024 px. -->\n <div class=\"lc-header__spacer\"></div>\n\n <!-- End of the bar: the end slot (e.g. a language link), theme toggle, context\n info and account menu as one group. It goes to the end of a second row\n when it doesn't fit beside the brand (AS-31). No role; the DOM order and\n so the focus order stay brand → nav → end. -->\n <div class=\"lc-header__end\">\n <ng-content select=\"[slot='end']\" />\n\n <!-- Theme toggle button -->\n @if (showThemeButton()) {\n <lc-button\n variant=\"ghost\"\n size=\"sm\"\n [iconOnly]=\"true\"\n [ariaLabel]=\"themeService.isDark() ? themeToLightAriaLabel() : themeToDarkAriaLabel()\"\n (clicked)=\"onThemeButtonClick()\"\n class=\"lc-header__theme-toggle\"\n >\n <lc-icon [name]=\"themeService.isDark() ? 'sun' : 'moon'\" size=\"sm\" [decorative]=\"true\" />\n </lc-button>\n }\n\n <!-- Context info (e.g. tenant, organization, project) -->\n @if (contextName()) {\n <button\n type=\"button\"\n class=\"lc-header__context-info\"\n [attr.aria-label]=\"contextButtonAriaLabel()\"\n (click)=\"contextClick.emit()\"\n >\n <!-- A real space between label and name: the visible text reads\n \"Tenant Acme Corp\", not \"TenantAcme Corp\" (WCAG 2.5.3). The\n tooltip sits on the name, so it is not a description of the button. -->\n @if (contextLabel()) {\n <span class=\"lc-header__context-label\">{{ contextLabel() }}</span>&ngsp;\n }\n <span class=\"lc-header__context-name\" [attr.title]=\"contextName()\">{{ contextName() }}</span>\n </button>\n }\n\n <!-- User profile menu -->\n @if (showUserMenu()) {\n <lc-menu\n [items]=\"menuItems()\"\n [isOpen]=\"isDropdownOpen()\"\n position=\"bottom-right\"\n minWidth=\"min(220px, calc(100vw - 2rem))\"\n maxWidth=\"min(18rem, calc(100vw - 2rem))\"\n [size]=\"menuSize()\"\n (itemClick)=\"onMenuItemClick($event)\"\n (closed)=\"closeDropdown()\"\n >\n <!-- Menu header with avatar, name and email -->\n @if (userName() || userEmail()) {\n <div header class=\"lc-header__menu-header\">\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n size=\"md\"\n />\n <div class=\"lc-header__menu-user-info\">\n @if (userName()) {\n <div class=\"lc-header__menu-user-name\">{{ userName() }}</div>\n }\n @if (userEmail()) {\n <div class=\"lc-header__menu-user-email\">{{ userEmail() }}</div>\n }\n </div>\n </div>\n }\n\n @if (userMenuTrigger() === 'avatar') {\n <button\n trigger\n class=\"lc-header__profile-trigger lc-header__profile-trigger--avatar\"\n type=\"button\"\n [attr.aria-label]=\"avatarTriggerAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-avatar\n [name]=\"userName()\"\n [src]=\"userAvatarSrc() || undefined\"\n [color]=\"userAvatarColor()\"\n [decorative]=\"true\"\n size=\"md\"\n />\n </button>\n } @else {\n <button\n trigger\n class=\"lc-header__profile-trigger\"\n type=\"button\"\n [attr.aria-label]=\"userMenuAriaLabel()\"\n [attr.aria-expanded]=\"isDropdownOpen()\"\n (click)=\"toggleDropdown()\"\n >\n <lc-icon name=\"user\" size=\"sm\" [decorative]=\"true\" />\n <lc-icon name=\"chevron-down\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </lc-menu>\n }\n </div>\n</header>\n\n<ng-template #brandLogo>\n <lc-logo\n [variant]=\"logo() ? 'full' : 'emblem'\"\n [size]=\"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() || title() || 'Logo'\"\n />\n</ng-template>\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ function labelWords(s) {
2
+ return s.normalize("NFKD").replace(/\([^()]*\)/g, " ").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
3
+ }
4
+ function containsWords(haystack, needle) {
5
+ return needle.length > 0 && haystack.some((_, i) => needle.every((word, k) => haystack[i + k] === word));
6
+ }
7
+ function nameWithText(label, text) {
8
+ const t = text.replace(/\s+/g, " ").trim();
9
+ const l = label.trim();
10
+ if (!/[\p{L}\p{N}]/u.test(t) || Array.from(t).length === 1 && !/\p{N}/u.test(t)) return l;
11
+ const labelW = labelWords(l);
12
+ const textW = labelWords(t);
13
+ if (containsWords(labelW, textW)) return l;
14
+ if (l === "") return t;
15
+ if (containsWords(textW, labelW)) return t;
16
+ return `${l}, ${t}`;
17
+ }
18
+ export { containsWords, labelWords, nameWithText };
19
+
20
+ //# sourceMappingURL=lifecorner-angular-ui-kit-internal-a11y.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-internal-a11y.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/internal/a11y/label-in-name.ts"],"sourcesContent":["/**\n * Accessible names that contain the visible text (WCAG 2.5.3 Label in Name,\n * AS-33). The containment test follows axe-core 4.14's\n * `label-content-name-mismatch`: both strings are compatibility-decomposed,\n * parentheticals are dropped, the rest is lower-cased and split into words of\n * letters and digits, and the visible text's words must occur as one\n * contiguous run in the name's words.\n */\n\n/**\n * The words of `s` as axe-core 4.14 compares them for Label in Name.\n * `toLowerCase`, not `toLocaleLowerCase`: server and browser must agree.\n */\nexport function labelWords(s: string): string[] {\n return s\n .normalize('NFKD')\n .replace(/\\([^()]*\\)/g, ' ')\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/** Whether `needle` occurs in `haystack` as one contiguous run of words. */\nexport function containsWords(haystack: readonly string[], needle: readonly string[]): boolean {\n return needle.length > 0 && haystack.some((_, i) => needle.every((word, k) => haystack[i + k] === word));\n}\n\n/**\n * Accessible name that contains the visible `text` (WCAG 2.5.3): `label`,\n * then `, text`, unless one already contains the other.\n *\n * - Text without letters or digits, or one non-digit character (a symbol or\n * an icon glyph, which axe reports as \"incomplete\"): `label`.\n * - The label already contains the text: `label`, unchanged.\n * - An empty label: `text`.\n * - The text contains the label: `text`, so nothing is read twice.\n * - Otherwise `label, text`.\n */\nexport function nameWithText(label: string, text: string): string {\n const t = text.replace(/\\s+/g, ' ').trim();\n const l = label.trim();\n if (!/[\\p{L}\\p{N}]/u.test(t) || (Array.from(t).length === 1 && !/\\p{N}/u.test(t))) return l;\n const labelW = labelWords(l);\n const textW = labelWords(t);\n if (containsWords(labelW, textW)) return l;\n if (l === '') return t;\n if (containsWords(textW, labelW)) return t;\n return `${l}, ${t}`;\n}\n"],"mappings":"AAaA,SAAgB,WAAW,GAAS;CAClC,OAAO,EACJ,UAAU,MAAM,CAAC,CACjB,QAAQ,eAAe,GAAG,CAAC,CAC3B,YAAW,CAAE,CACb,MAAM,iBAAiB,CAAC,CACxB,OAAO,OAAO;AACnB;AAGA,SAAgB,cAAc,UAA6B,QAAyB;CAClF,OAAO,OAAO,SAAS,KAAK,SAAS,MAAM,GAAG,MAAM,OAAO,OAAO,MAAM,MAAM,SAAS,IAAI,OAAO,IAAI,CAAC;AACzG;AAaA,SAAgB,aAAa,OAAe,MAAY;CACtD,MAAM,IAAI,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAI;CACxC,MAAM,IAAI,MAAM,KAAI;CACpB,IAAI,CAAC,gBAAgB,KAAK,CAAC,KAAM,MAAM,KAAK,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,SAAS,KAAK,CAAC,GAAI,OAAO;CAC1F,MAAM,SAAS,WAAW,CAAC;CAC3B,MAAM,QAAQ,WAAW,CAAC;CAC1B,IAAI,cAAc,QAAQ,KAAK,GAAG,OAAO;CACzC,IAAI,MAAM,IAAI,OAAO;CACrB,IAAI,cAAc,OAAO,MAAM,GAAG,OAAO;CACzC,OAAO,GAAG,EAAC,IAAK;AAClB"}
@@ -1,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, computed, input, output } from "@angular/core";
3
3
  import { NgClass } from "@angular/common";
4
+ import { nameWithText } from "@lifecorner/angular-ui-kit/internal/a11y";
4
5
  var PaginationComponent = class PaginationComponent {
5
6
  currentPage = input(1, ...ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []);
6
7
  totalItems = input(0, ...ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []);
@@ -16,6 +17,8 @@ var PaginationComponent = class PaginationComponent {
16
17
  pageAriaLabel = input("Page {page}", ...ngDevMode ? [{ debugName: "pageAriaLabel" }] : /* istanbul ignore next */ []);
17
18
  infoText = input("Showing {first} to {last} of {total} items", ...ngDevMode ? [{ debugName: "infoText" }] : /* istanbul ignore next */ []);
18
19
  pageChange = output();
20
+ previousButtonAriaLabel = computed(() => nameWithText(this.previousAriaLabel(), this.previousLabel()), ...ngDevMode ? [{ debugName: "previousButtonAriaLabel" }] : /* istanbul ignore next */ []);
21
+ nextButtonAriaLabel = computed(() => nameWithText(this.nextAriaLabel(), this.nextLabel()), ...ngDevMode ? [{ debugName: "nextButtonAriaLabel" }] : /* istanbul ignore next */ []);
19
22
  totalPages = computed(() => {
20
23
  const total = this.totalItems();
21
24
  const size = this.pageSize();
@@ -200,7 +203,7 @@ var PaginationComponent = class PaginationComponent {
200
203
  },
201
204
  outputs: { pageChange: "pageChange" },
202
205
  ngImport: i0,
203
- template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
206
+ template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
204
207
  styles: [".lc-pagination{display:flex;align-items:center;justify-content:center;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-pagination--sm{font-size:var(--font-size-xs)}.lc-pagination--sm .lc-pagination__button{min-width:1.75rem;height:1.75rem;padding:0 var(--spacing-1)}.lc-pagination--sm .lc-pagination__icon{width:.875rem;height:.875rem}.lc-pagination--sm .lc-pagination__button-text{font-size:var(--font-size-xs)}.lc-pagination--md{font-size:var(--font-size-sm)}.lc-pagination--md .lc-pagination__button{min-width:2.25rem;height:2.25rem;padding:0 var(--spacing-2)}.lc-pagination--md .lc-pagination__icon{width:1rem;height:1rem}.lc-pagination--md .lc-pagination__button-text{font-size:var(--font-size-sm)}.lc-pagination--lg{font-size:var(--font-size-base)}.lc-pagination--lg .lc-pagination__button{min-width:2.75rem;height:2.75rem;padding:0 var(--spacing-3)}.lc-pagination--lg .lc-pagination__icon{width:1.25rem;height:1.25rem}.lc-pagination--lg .lc-pagination__button-text{font-size:var(--font-size-base)}.lc-pagination__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--spacing-1);border:1px solid var(--color-border);background-color:var(--color-surface-2);color:var(--color-text-secondary);border-radius:var(--border-radius-md);font-weight:500;cursor:pointer;transition:color var(--animation-duration-base) var(--animation-timing-ease),background-color var(--animation-duration-base) var(--animation-timing-ease),border-color var(--animation-duration-base) var(--animation-timing-ease),transform var(--animation-duration-base) var(--animation-timing-ease);-webkit-user-select:none;user-select:none}.lc-pagination__button:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-border-strong);color:var(--color-text-primary)}.lc-pagination__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-pagination__button:active:not(:disabled){background-color:var(--color-surface-selected);transform:translateY(1px)}.lc-pagination__button:disabled{opacity:.5;cursor:not-allowed}.lc-pagination__button--active{background-color:var(--color-primary-fill);border-color:var(--color-primary-fill);color:var(--color-on-primary);font-weight:600}.lc-pagination__button--active:hover{background-color:var(--color-primary-fill-hover);border-color:var(--color-primary-fill-hover);color:var(--color-on-primary)}@media(max-width:640px){.lc-pagination__button-text:not(.lc-pagination__button--active .lc-pagination__button-text){display:none}}.lc-pagination__icon{flex-shrink:0}.lc-pagination__pages{display:flex;align-items:center;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.lc-pagination__info{text-align:center;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin-top:var(--spacing-3)}@media(max-width:640px){.lc-pagination{gap:var(--spacing-1)}.lc-pagination__pages{gap:var(--spacing-0-5)}.lc-pagination__button--page:not(.lc-pagination__button--active){display:none}.lc-pagination__button--page:not(.lc-pagination__button--active):first-child,.lc-pagination__button--page:not(.lc-pagination__button--active):last-child{display:inline-flex}.lc-pagination__button--page:not(.lc-pagination__button--active):nth-child(n-1):nth-child(-n+1){display:inline-flex}}@media(prefers-reduced-motion:reduce){.lc-pagination__button{transition:none}}\n"],
205
208
  dependencies: [{
206
209
  kind: "directive",
@@ -223,7 +226,7 @@ i0.ɵɵngDeclareClassMetadata({
223
226
  standalone: true,
224
227
  imports: [NgClass],
225
228
  changeDetection: ChangeDetectionStrategy.OnPush,
226
- template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
229
+ template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
227
230
  styles: [".lc-pagination{display:flex;align-items:center;justify-content:center;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-pagination--sm{font-size:var(--font-size-xs)}.lc-pagination--sm .lc-pagination__button{min-width:1.75rem;height:1.75rem;padding:0 var(--spacing-1)}.lc-pagination--sm .lc-pagination__icon{width:.875rem;height:.875rem}.lc-pagination--sm .lc-pagination__button-text{font-size:var(--font-size-xs)}.lc-pagination--md{font-size:var(--font-size-sm)}.lc-pagination--md .lc-pagination__button{min-width:2.25rem;height:2.25rem;padding:0 var(--spacing-2)}.lc-pagination--md .lc-pagination__icon{width:1rem;height:1rem}.lc-pagination--md .lc-pagination__button-text{font-size:var(--font-size-sm)}.lc-pagination--lg{font-size:var(--font-size-base)}.lc-pagination--lg .lc-pagination__button{min-width:2.75rem;height:2.75rem;padding:0 var(--spacing-3)}.lc-pagination--lg .lc-pagination__icon{width:1.25rem;height:1.25rem}.lc-pagination--lg .lc-pagination__button-text{font-size:var(--font-size-base)}.lc-pagination__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--spacing-1);border:1px solid var(--color-border);background-color:var(--color-surface-2);color:var(--color-text-secondary);border-radius:var(--border-radius-md);font-weight:500;cursor:pointer;transition:color var(--animation-duration-base) var(--animation-timing-ease),background-color var(--animation-duration-base) var(--animation-timing-ease),border-color var(--animation-duration-base) var(--animation-timing-ease),transform var(--animation-duration-base) var(--animation-timing-ease);-webkit-user-select:none;user-select:none}.lc-pagination__button:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-border-strong);color:var(--color-text-primary)}.lc-pagination__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-pagination__button:active:not(:disabled){background-color:var(--color-surface-selected);transform:translateY(1px)}.lc-pagination__button:disabled{opacity:.5;cursor:not-allowed}.lc-pagination__button--active{background-color:var(--color-primary-fill);border-color:var(--color-primary-fill);color:var(--color-on-primary);font-weight:600}.lc-pagination__button--active:hover{background-color:var(--color-primary-fill-hover);border-color:var(--color-primary-fill-hover);color:var(--color-on-primary)}@media(max-width:640px){.lc-pagination__button-text:not(.lc-pagination__button--active .lc-pagination__button-text){display:none}}.lc-pagination__icon{flex-shrink:0}.lc-pagination__pages{display:flex;align-items:center;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.lc-pagination__info{text-align:center;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin-top:var(--spacing-3)}@media(max-width:640px){.lc-pagination{gap:var(--spacing-1)}.lc-pagination__pages{gap:var(--spacing-0-5)}.lc-pagination__button--page:not(.lc-pagination__button--active){display:none}.lc-pagination__button--page:not(.lc-pagination__button--active):first-child,.lc-pagination__button--page:not(.lc-pagination__button--active):last-child{display:inline-flex}.lc-pagination__button--page:not(.lc-pagination__button--active):nth-child(n-1):nth-child(-n+1){display:inline-flex}}@media(prefers-reduced-motion:reduce){.lc-pagination__button{transition:none}}\n"]
228
231
  }]
229
232
  }],
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-pagination.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.ts","../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\n/**\n * Pagination component for navigating through pages of content.\n *\n * Features:\n * - Configurable page size\n * - Previous/Next navigation\n * - Direct page number access\n * - Ellipsis for large page ranges\n * - Size variants (sm, md, lg)\n * - Accessible with ARIA attributes\n * - Optional item count display\n * - All UI strings overridable via inputs (i18n)\n *\n * @example\n * ```html\n * <lc-pagination\n * [currentPage]=\"currentPage\"\n * [totalItems]=\"totalItems\"\n * [pageSize]=\"10\"\n * (pageChange)=\"onPageChange($event)\"\n * ></lc-pagination>\n * ```\n */\n@Component({\n selector: 'lc-pagination',\n standalone: true,\n imports: [NgClass],\n templateUrl: './pagination.component.html',\n styleUrls: ['./pagination.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PaginationComponent {\n readonly currentPage = input(1);\n readonly totalItems = input(0);\n readonly pageSize = input(10);\n readonly size = input<PaginationSize>('md');\n readonly maxVisiblePages = input(7);\n readonly ariaLabel = input('Pagination');\n readonly showInfo = input(false);\n\n // ── UI strings (override for i18n) ──────────────────────────────────────\n /** Visible text of the previous button */\n readonly previousLabel = input('Previous');\n /** Visible text of the next button */\n readonly nextLabel = input('Next');\n /** Accessible name of the previous button (the visible text is hidden on small screens) */\n readonly previousAriaLabel = input('Previous page');\n /** Accessible name of the next button */\n readonly nextAriaLabel = input('Next page');\n /** Accessible name of a page button; `{page}` is replaced with the page number */\n readonly pageAriaLabel = input('Page {page}');\n /**\n * Info line shown when `showInfo` is true; `{first}`, `{last}` and `{total}`\n * are replaced with the item range and total.\n */\n readonly infoText = input('Showing {first} to {last} of {total} items');\n\n readonly pageChange = output<number>();\n\n // Computed properties\n totalPages = computed(() => {\n const total = this.totalItems();\n const size = this.pageSize();\n return total > 0 ? Math.ceil(total / size) : 0;\n });\n\n firstItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n return (page - 1) * size + 1;\n });\n\n lastItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n const total = this.totalItems();\n const lastIndex = page * size;\n return Math.min(lastIndex, total);\n });\n\n isFirstPage = computed(() => this.currentPage() === 1);\n\n isLastPage = computed(() => this.currentPage() === this.totalPages());\n\n paginationClasses = computed(() => {\n const classes = ['lc-pagination'];\n classes.push(`lc-pagination--${this.size()}`);\n return classes.join(' ');\n });\n\n /**\n * Calculate visible page numbers with ellipsis support\n * Returns an array where -1 represents an ellipsis\n */\n visiblePages = computed(() => {\n const current = this.currentPage();\n const total = this.totalPages();\n const maxVisible = this.maxVisiblePages();\n\n if (total <= maxVisible) {\n // Show all pages if total is less than max visible\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: number[] = [];\n const sidePages = Math.floor((maxVisible - 3) / 2); // Reserve spots for first, last, and ellipsis\n\n // Always show first page\n pages.push(1);\n\n if (current <= sidePages + 2) {\n // Near the start: show 1, 2, 3, 4, 5, ..., last\n for (let i = 2; i <= maxVisible - 2; i++) {\n pages.push(i);\n }\n pages.push(-1); // Ellipsis\n pages.push(total);\n } else if (current >= total - sidePages - 1) {\n // Near the end: show 1, ..., n-4, n-3, n-2, n-1, n\n pages.push(-1); // Ellipsis\n for (let i = total - (maxVisible - 3); i <= total; i++) {\n pages.push(i);\n }\n } else {\n // In the middle: show 1, ..., current-1, current, current+1, ..., last\n pages.push(-1); // Left ellipsis\n for (let i = current - sidePages; i <= current + sidePages; i++) {\n if (i > 1 && i < total) {\n pages.push(i);\n }\n }\n pages.push(-1); // Right ellipsis\n pages.push(total);\n }\n\n return pages;\n });\n\n // Navigation methods\n goToPreviousPage(): void {\n if (!this.isFirstPage()) {\n const newPage = this.currentPage() - 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToNextPage(): void {\n if (!this.isLastPage()) {\n const newPage = this.currentPage() + 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToPage(page: number): void {\n if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {\n this.pageChange.emit(page);\n }\n }\n\n /** Resolved info line (`infoText` with placeholders filled in) */\n readonly infoLine = computed(() =>\n this.infoText()\n .replace('{first}', String(this.firstItemIndex()))\n .replace('{last}', String(this.lastItemIndex()))\n .replace('{total}', String(this.totalItems())),\n );\n\n /** Accessible name for the button of `page` */\n getPageAriaLabel(page: number): string {\n return this.pageAriaLabel().replace('{page}', String(page));\n }\n\n // Helper methods\n isCurrentPage(page: number): boolean {\n return page === this.currentPage();\n }\n\n isEllipsis(page: number): boolean {\n return page === -1;\n }\n\n shouldRender(): boolean {\n const total = this.totalPages();\n return total > 1;\n }\n}\n","@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-pagination.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.ts","../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\n/**\n * Pagination component for navigating through pages of content.\n *\n * Features:\n * - Configurable page size\n * - Previous/Next navigation\n * - Direct page number access\n * - Ellipsis for large page ranges\n * - Size variants (sm, md, lg)\n * - Accessible with ARIA attributes\n * - Optional item count display\n * - All UI strings overridable via inputs (i18n)\n *\n * @example\n * ```html\n * <lc-pagination\n * [currentPage]=\"currentPage\"\n * [totalItems]=\"totalItems\"\n * [pageSize]=\"10\"\n * (pageChange)=\"onPageChange($event)\"\n * ></lc-pagination>\n * ```\n */\n@Component({\n selector: 'lc-pagination',\n standalone: true,\n imports: [NgClass],\n templateUrl: './pagination.component.html',\n styleUrls: ['./pagination.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PaginationComponent {\n readonly currentPage = input(1);\n readonly totalItems = input(0);\n readonly pageSize = input(10);\n readonly size = input<PaginationSize>('md');\n readonly maxVisiblePages = input(7);\n readonly ariaLabel = input('Pagination');\n readonly showInfo = input(false);\n\n // ── UI strings (override for i18n) ──────────────────────────────────────\n /** Visible text of the previous button */\n readonly previousLabel = input('Previous');\n /** Visible text of the next button */\n readonly nextLabel = input('Next');\n /**\n * Accessible name of the previous button (the visible text is hidden on\n * small screens). `previousLabel` is appended when this doesn't contain it\n * (WCAG 2.5.3 Label in Name), e.g. `Vorherige Seite, Zurück`.\n * @default 'Previous page'\n */\n readonly previousAriaLabel = input('Previous page');\n /**\n * Accessible name of the next button. `nextLabel` is appended when this\n * doesn't contain it (WCAG 2.5.3 Label in Name), e.g. `Nächste Seite, Weiter`.\n * @default 'Next page'\n */\n readonly nextAriaLabel = input('Next page');\n /** Accessible name of a page button; `{page}` is replaced with the page number */\n readonly pageAriaLabel = input('Page {page}');\n /**\n * Info line shown when `showInfo` is true; `{first}`, `{last}` and `{total}`\n * are replaced with the item range and total.\n */\n readonly infoText = input('Showing {first} to {last} of {total} items');\n\n readonly pageChange = output<number>();\n\n /** Name of the previous button: its label, then the visible text unless contained (AS-33). */\n protected readonly previousButtonAriaLabel = computed(() =>\n nameWithText(this.previousAriaLabel(), this.previousLabel()),\n );\n\n /** Name of the next button: its label, then the visible text unless contained (AS-33). */\n protected readonly nextButtonAriaLabel = computed(() => nameWithText(this.nextAriaLabel(), this.nextLabel()));\n\n // Computed properties\n totalPages = computed(() => {\n const total = this.totalItems();\n const size = this.pageSize();\n return total > 0 ? Math.ceil(total / size) : 0;\n });\n\n firstItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n return (page - 1) * size + 1;\n });\n\n lastItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n const total = this.totalItems();\n const lastIndex = page * size;\n return Math.min(lastIndex, total);\n });\n\n isFirstPage = computed(() => this.currentPage() === 1);\n\n isLastPage = computed(() => this.currentPage() === this.totalPages());\n\n paginationClasses = computed(() => {\n const classes = ['lc-pagination'];\n classes.push(`lc-pagination--${this.size()}`);\n return classes.join(' ');\n });\n\n /**\n * Calculate visible page numbers with ellipsis support\n * Returns an array where -1 represents an ellipsis\n */\n visiblePages = computed(() => {\n const current = this.currentPage();\n const total = this.totalPages();\n const maxVisible = this.maxVisiblePages();\n\n if (total <= maxVisible) {\n // Show all pages if total is less than max visible\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: number[] = [];\n const sidePages = Math.floor((maxVisible - 3) / 2); // Reserve spots for first, last, and ellipsis\n\n // Always show first page\n pages.push(1);\n\n if (current <= sidePages + 2) {\n // Near the start: show 1, 2, 3, 4, 5, ..., last\n for (let i = 2; i <= maxVisible - 2; i++) {\n pages.push(i);\n }\n pages.push(-1); // Ellipsis\n pages.push(total);\n } else if (current >= total - sidePages - 1) {\n // Near the end: show 1, ..., n-4, n-3, n-2, n-1, n\n pages.push(-1); // Ellipsis\n for (let i = total - (maxVisible - 3); i <= total; i++) {\n pages.push(i);\n }\n } else {\n // In the middle: show 1, ..., current-1, current, current+1, ..., last\n pages.push(-1); // Left ellipsis\n for (let i = current - sidePages; i <= current + sidePages; i++) {\n if (i > 1 && i < total) {\n pages.push(i);\n }\n }\n pages.push(-1); // Right ellipsis\n pages.push(total);\n }\n\n return pages;\n });\n\n // Navigation methods\n goToPreviousPage(): void {\n if (!this.isFirstPage()) {\n const newPage = this.currentPage() - 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToNextPage(): void {\n if (!this.isLastPage()) {\n const newPage = this.currentPage() + 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToPage(page: number): void {\n if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {\n this.pageChange.emit(page);\n }\n }\n\n /** Resolved info line (`infoText` with placeholders filled in) */\n readonly infoLine = computed(() =>\n this.infoText()\n .replace('{first}', String(this.firstItemIndex()))\n .replace('{last}', String(this.lastItemIndex()))\n .replace('{total}', String(this.totalItems())),\n );\n\n /** Accessible name for the button of `page` */\n getPageAriaLabel(page: number): string {\n return this.pageAriaLabel().replace('{page}', String(page));\n }\n\n // Helper methods\n isCurrentPage(page: number): boolean {\n return page === this.currentPage();\n }\n\n isEllipsis(page: number): boolean {\n return page === -1;\n }\n\n shouldRender(): boolean {\n const total = this.totalPages();\n return total > 1;\n }\n}\n","@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { CommonModule } from "@angular/common";
4
4
  import { NG_VALUE_ACCESSOR } from "@angular/forms";
5
5
  import { DomSanitizer } from "@angular/platform-browser";
6
6
  import { isSafeImageUrl, isSafeLinkUrl } from "@lifecorner/angular-ui-kit/internal/url";
7
+ import { nameWithText } from "@lifecorner/angular-ui-kit/internal/a11y";
7
8
  const HTML_ESCAPES = {
8
9
  "&": "&amp;",
9
10
  "<": "&lt;",
@@ -298,6 +299,9 @@ var RichTextEditorComponent = class RichTextEditorComponent {
298
299
  hr: "Horizontal Rule"
299
300
  }[action] || action;
300
301
  }
302
+ getActionName(action) {
303
+ return nameWithText(this.getActionTitle(action), this.getActionLabel(action));
304
+ }
301
305
  markdownToHtml(md) {
302
306
  if (!md) return "";
303
307
  let html = escapeHtml(md);
@@ -523,7 +527,7 @@ var RichTextEditorComponent = class RichTextEditorComponent {
523
527
  isSignal: true
524
528
  }],
525
529
  ngImport: i0,
526
- template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionTitle(action)\"\n [attr.aria-label]=\"getActionTitle(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
530
+ template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
527
531
  styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}\n"],
528
532
  dependencies: [{
529
533
  kind: "ngmodule",
@@ -549,7 +553,7 @@ i0.ɵɵngDeclareClassMetadata({
549
553
  useExisting: forwardRef(() => RichTextEditorComponent),
550
554
  multi: true
551
555
  }],
552
- template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionTitle(action)\"\n [attr.aria-label]=\"getActionTitle(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
556
+ template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
553
557
  styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}\n"]
554
558
  }]
555
559
  }],