@lifecorner/angular-ui-kit 3.34.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +1 -0
  2. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
  3. package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
  4. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-button.mjs +6 -6
  6. package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-card.mjs +82 -16
  8. package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +23 -4
  10. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +43 -5
  12. package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs +30 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
  16. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +45 -8
  17. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  18. package/fesm2022/lifecorner-angular-ui-kit-hero.mjs +57 -5
  19. package/fesm2022/lifecorner-angular-ui-kit-hero.mjs.map +1 -1
  20. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs +6 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs.map +1 -1
  22. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +24 -5
  23. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  24. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs +2 -4
  25. package/fesm2022/lifecorner-angular-ui-kit-modal.mjs.map +1 -1
  26. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +41 -1
  27. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  28. package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs +47 -5
  29. package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs.map +1 -1
  30. package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs +203 -0
  31. package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs.map +1 -0
  32. package/fesm2022/lifecorner-angular-ui-kit-table.mjs +25 -4
  33. package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
  34. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +3 -0
  35. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  36. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
  37. package/fesm2022/lifecorner-angular-ui-kit.mjs +1 -0
  38. package/package.json +5 -1
  39. package/src/styles/theme-contrast.spec.ts +74 -0
  40. package/types/lifecorner-angular-ui-kit-alert.d.ts +12 -2
  41. package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +5 -1
  42. package/types/lifecorner-angular-ui-kit-button.d.ts +7 -3
  43. package/types/lifecorner-angular-ui-kit-card.d.ts +51 -3
  44. package/types/lifecorner-angular-ui-kit-combobox.d.ts +36 -0
  45. package/types/lifecorner-angular-ui-kit-description-list.d.ts +40 -0
  46. package/types/lifecorner-angular-ui-kit-drawer.d.ts +37 -1
  47. package/types/lifecorner-angular-ui-kit-gallery.d.ts +3 -1
  48. package/types/lifecorner-angular-ui-kit-header.d.ts +44 -2
  49. package/types/lifecorner-angular-ui-kit-hero.d.ts +32 -0
  50. package/types/lifecorner-angular-ui-kit-internal-overlay.d.ts +9 -1
  51. package/types/lifecorner-angular-ui-kit-list.d.ts +17 -0
  52. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +42 -1
  53. package/types/lifecorner-angular-ui-kit-stacked-bar-chart.d.ts +36 -0
  54. package/types/lifecorner-angular-ui-kit-state-strip.d.ts +107 -0
  55. package/types/lifecorner-angular-ui-kit-table.d.ts +37 -0
  56. package/types/lifecorner-angular-ui-kit-textarea.d.ts +17 -0
  57. package/types/lifecorner-angular-ui-kit-toast.d.ts +4 -2
  58. package/types/lifecorner-angular-ui-kit.d.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable.\n *\n * Bottom stacks show the newest toast nearest the edge (3.33); top stacks the\n * oldest at the top. The toasts of a stack are 8 px (`--spacing-2`) apart\n * (3.33.1), within a live region and across the two; an empty region and the\n * last toast add no space, and the stack keeps its 16 px padding. A `top-*`\n * stack lies over the page on purpose (a toast stays visible wherever the\n * page is scrolled), so in the shell it can cover a title right under the\n * header (at 360 px a toast at y 72 over an h1 at y 80): prefer `bottom-*`\n * there.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n /**\n * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its\n * newest toast. That region gets `lc-toast-outlet__region--latest` and is\n * laid out at the edge, so the newest toast sits nearest the edge (and, in\n * a scrolling stack, in view at rest). DOM and announcement order stay.\n */\n protected readonly latestLive = computed(() => {\n const latest = new Map<ToastPosition, ToastLive>();\n for (const toast of this.toastService.toasts()) {\n if (toast.position.startsWith('bottom-')) {\n latest.set(toast.position, ToastOutletComponent.liveFor(toast));\n }\n }\n return latest;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":";;;;;;AAuDA,IAAa,eAAb,MAAa,aAAY;CAIP;CAKC,YAAY,OAAgB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAE/B,aAAa;CACtB,iBAAiB;CACjB,iCAAiB,IAAI,IAAG;CAEf,YAAY,kBAAkB,OAAO,WAAW,CAAC;CAElE,cAAA;EACE,KAAK,SAAS,KAAK,UAAU,WAAU;CACzC;CAKA,KAAK,QAAmB;EACtB,MAAM,QAAe;GACnB,IAAI,KAAK,WAAU;GACnB,SAAS,OAAO;GAChB,SAAS,OAAO,WAAW;GAC3B,UAAU,OAAO,YAAY;GAC7B,UAAU,OAAO,aAAa,SAAY,OAAO,WAAW;GAC5D,iBAAiB,OAAO,oBAAoB,SAAY,OAAO,kBAAkB;GACjF,QAAQ,OAAO;EAChB;EAGD,IAAI,gBAAgB,KAAK,UAAS;EAGlC,IAAI,cAAc,UAAU,KAAK,YAAY;GAC3C,MAAM,cAAc,cAAc;GAClC,IAAI,aAAa;IACf,KAAK,aAAa,YAAY,EAAE;IAChC,gBAAgB,cAAc,MAAM,CAAC;GACvC;EACF;EAEA,KAAK,UAAU,IAAI,CAAC,GAAG,eAAe,KAAK,CAAC;EAG5C,IAAI,MAAM,WAAW,KAAK,KAAK,WAAW;GACxC,MAAM,YAAY,iBAAgB;IAChC,KAAK,MAAM,MAAM,EAAE;GACrB,GAAG,MAAM,QAAQ;GAEjB,KAAK,eAAe,IAAI,MAAM,IAAI,SAAS;EAC7C;EAEA,OAAO,MAAM;CACf;CAKA,MAAM,IAAU;EACd,KAAK,YAAY,EAAE;CACrB;CAKA,WAAQ;EAEN,AADsB,KAAK,UAC3B,CAAA,CAAc,SAAS,UAAS;GAC9B,KAAK,aAAa,MAAM,EAAE;EAC5B,CAAC;EACD,KAAK,UAAU,IAAI,CAAA,CAAE;CACvB;CAKQ,aAAU;EAChB,OAAO,SAAS,EAAE,KAAK,eAAc,GAAI,KAAK,IAAG;CACnD;CAKQ,YAAY,IAAU;EAC5B,KAAK,aAAa,EAAE;EACpB,MAAM,gBAAgB,KAAK,UAAS;EACpC,KAAK,UAAU,IAAI,cAAc,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC;CAC7D;CAKQ,aAAa,IAAU;EAC7B,MAAM,YAAY,KAAK,eAAe,IAAI,EAAE;EAC5C,IAAI,cAAc,QAAW;GAC3B,aAAa,SAAS;GACtB,KAAK,eAAe,OAAO,EAAE;EAC/B;CACF;CAxGW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,YAFX;CAAM,CAAA;;AAEP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAHxB,MAAA;EAAW,MAAA,CAAA,EACV,YAAY,OACb,CAAA;;;;ACAD,IAAa,iBAAb,MAAa,eAAc;CAIzB,QAAQ,MAAM,SAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQ/B,WAAW,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,iBAAiB,MAAM,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAKnC,SAAS,OAAM;CAKd,cAAc,eAAc;EAEpC,OAAO,sBADO,KAAK,MACU,CAAA,CAAM;CACrC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,mBAAmB,eAAc;EACzC,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,WAAW,eAAc;EACjC,IAAI,CAAC,KAAK,SAAQ,GAAI,OAAO;EAC7B,MAAM,UAAU,KAAK,MAAK,CAAE,CAAC;EAC7B,OAAO,YAAY,WAAW,YAAY,YAAY,UAAU;CAClE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,WAAW,eAAc;EACjC,IAAI,CAAC,KAAK,SAAQ,GAAI,OAAO;EAC7B,MAAM,UAAU,KAAK,MAAK,CAAE,CAAC;EAC7B,OAAO,YAAY,WAAW,YAAY,YAAY,cAAc;CACtE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,cAAc,eAAc;EAOpC,OAAO;GALL,SAAS;GACT,OAAO;GACP,SAAS;GACT,MAAM;EAED,EAAM,KAAK,MAAK,CAAE,CAAC;CAC5B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,aAAa,OAAY;EACjC,MAAM,eAAc;EACpB,MAAM,gBAAe;EACrB,KAAK,OAAO,KAAK,KAAK,MAAK,CAAE,CAAC,EAAE;CAClC;CAKU,cAAc,OAAY;EAClC,MAAM,eAAc;EACpB,MAAM,gBAAe;EACrB,MAAM,SAAS,KAAK,MAAK,CAAE,CAAC;EAC5B,IAAI,QAAQ;GACV,OAAO,QAAO;GACd,KAAK,OAAO,KAAK,KAAK,MAAK,CAAE,CAAC,EAAE;EAClC;CACF;CAxFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAc,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAd,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAc,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,QAAA,SAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,aAAA;GAAA,kBAAA;GAAA,oBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UCtD3B;EA8BA,QAAA,CAAA,o3HAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDSY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eACR,CACb,iBAAiB;GAAC;GAAa;GAAmB;GAAe;GAA2B;EAAuB,CAAC,CACrH;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAYU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAc,YAAA,CAAA;EAlB1B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAU,YACR;GAAI,SACP,CAAC,aAAa;GAAC,eACT,CACb,iBAAiB;IAAC;IAAa;IAAmB;IAAe;IAA2B;GAAuB,CAAC,CACrH;GAAA,eAGc,kBAAkB;GAAI,iBACpB,wBAAwB;GAAM,MACzC;IACJ,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,sBAAsB;GACvB;GAAA,UAAA;GAAA,QAAA,CAAA,o3HAAA;EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AE5CH,MAAM,YAAsC;CAC1C;CACA;CACA;CACA;CACA;CACA;AACD;AA4DD,IAAa,uBAAb,MAAa,qBAAoB;CACZ,YAAY;CACZ,aAAmC,CAAC,aAAa,QAAQ;CAE3D,eAAe,OAAO,YAAY;CAChC,SAAS,OAAO,oBAAoB;CAG9C,iBAAiB,MAAM,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpC,SAAS,eAAc;EACtC,MAAM,yBAAS,IAAI,IAAG;EACtB,KAAK,MAAM,SAAS,KAAK,aAAa,OAAM,GAAI;GAC9C,MAAM,MAAM,GAAG,MAAM,SAAQ,GAAI,qBAAqB,QAAQ,KAAK;GACnE,MAAM,QAAQ,OAAO,IAAI,GAAG;GAC5B,IAAI,OACF,MAAM,KAAK,KAAK;QAEhB,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;EAE3B;EACA,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAQiB,aAAa,eAAc;EAC5C,MAAM,yBAAS,IAAI,IAAG;EACtB,KAAK,MAAM,SAAS,KAAK,aAAa,OAAM,GAC1C,IAAI,MAAM,SAAS,WAAW,SAAS,GACrC,OAAO,IAAI,MAAM,UAAU,qBAAqB,QAAQ,KAAK,CAAC;EAGlE,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,UAAU,UAAyB,MAAe;EAC1D,OAAO,KAAK,OAAM,CAAE,CAAC,IAAI,GAAG,SAAQ,GAAI,MAAM,KAAK,CAAA;CACrD;CAEU,SAAS,IAAU;EAC3B,KAAK,aAAa,MAAM,EAAE;CAC5B;CAGQ,OAAO,QAAQ,OAAY;EACjC,OAAO,MAAM,YAAY,WAAW,MAAM,YAAY,YAAY,cAAc;CAClF;CApDW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAoB,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,gBAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,sCAAA;GAAA,yCAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UC3EjC;EAkBA,QAAA,CAAA,gnEAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD8CY;GAAc,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;GAAA;GAAA,SAAA,CAAA,QAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAWb,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EAdhC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAiB,YACf;GAAI,SACP,CAAC,cAAc;GAAC,iBAGR,wBAAwB;GAAM,MACzC;IAGJ,wCAAwC;IACxC,2CAA2C;GAC5C;GAAA,UAAA;GAAA,QAAA,CAAA,gnEAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable.\n *\n * Bottom stacks show the newest toast nearest the edge (3.33). A top stack\n * shows its assertive region (error, warning) above its polite one (success,\n * info), each oldest first, so a newer error sits above an older success\n * (3.35, 053-n2). The toasts of a stack are 8 px (`--spacing-2`) apart\n * (3.33.1), within a live region and across the two; an empty region and the\n * last toast add no space, and the stack keeps its 16 px padding. A `top-*`\n * stack lies over the page on purpose (a toast stays visible wherever the\n * page is scrolled), so in the shell it can cover a title right under the\n * header (at 360 px a toast at y 72 over an h1 at y 80): prefer `bottom-*`\n * there.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n /**\n * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its\n * newest toast. That region gets `lc-toast-outlet__region--latest` and is\n * laid out at the edge, so the newest toast sits nearest the edge (and, in\n * a scrolling stack, in view at rest). DOM and announcement order stay.\n */\n protected readonly latestLive = computed(() => {\n const latest = new Map<ToastPosition, ToastLive>();\n for (const toast of this.toastService.toasts()) {\n if (toast.position.startsWith('bottom-')) {\n latest.set(toast.position, ToastOutletComponent.liveFor(toast));\n }\n }\n return latest;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
@@ -54,6 +54,7 @@ export * from "@lifecorner/angular-ui-kit/app-shell";
54
54
  export * from "@lifecorner/angular-ui-kit/avatar";
55
55
  export * from "@lifecorner/angular-ui-kit/badge";
56
56
  export * from "@lifecorner/angular-ui-kit/status-dot";
57
+ export * from "@lifecorner/angular-ui-kit/state-strip";
57
58
  export * from "@lifecorner/angular-ui-kit/toggle-group";
58
59
  export * from "@lifecorner/angular-ui-kit/list";
59
60
  export * from "@lifecorner/angular-ui-kit/chip";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifecorner/angular-ui-kit",
3
- "version": "3.34.0",
3
+ "version": "3.36.0",
4
4
  "description": "LifeCorner Design System – Angular UI component library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -462,6 +462,10 @@
462
462
  "types": "./types/lifecorner-angular-ui-kit-stat-trend.d.ts",
463
463
  "default": "./fesm2022/lifecorner-angular-ui-kit-stat-trend.mjs"
464
464
  },
465
+ "./state-strip": {
466
+ "types": "./types/lifecorner-angular-ui-kit-state-strip.d.ts",
467
+ "default": "./fesm2022/lifecorner-angular-ui-kit-state-strip.mjs"
468
+ },
465
469
  "./status-dot": {
466
470
  "types": "./types/lifecorner-angular-ui-kit-status-dot.d.ts",
467
471
  "default": "./fesm2022/lifecorner-angular-ui-kit-status-dot.mjs"
@@ -1647,6 +1647,8 @@ describe('Component pairings of 3.25.0 (request 039)', () => {
1647
1647
  ['textarea', '.lc-textarea--error:focus', '2px solid var(--color-error-600)', '0'],
1648
1648
  ['number', '.number-input--error .number-input__control:focus-within', '2px solid var(--color-error-600)', '0'],
1649
1649
  ['combobox', '.lc-combobox--error .lc-combobox__input-wrapper:focus-within', '2px solid var(--color-error-default)', '0'],
1650
+ // 3.35 (055 D-3): the rest focus ring, so `focus()` from code shows it too.
1651
+ ['combobox', '.lc-combobox__input-wrapper:focus-within', primary, '0'],
1650
1652
  ];
1651
1653
 
1652
1654
  it.each(rings)('AC-2: %s %s draws a solid 2 px outline, no halo', (sheet, selector, outline, offset) => {
@@ -1958,3 +1960,75 @@ describe('Component pairings of 3.30 (request 047)', () => {
1958
1960
  expect(Math.min(got.textOnFill, got.textOnHoverFill)).toBeGreaterThanOrEqual(4.5);
1959
1961
  });
1960
1962
  });
1963
+
1964
+ /**
1965
+ * Request 056 (3.36, design 056 §5): the state strip's marks, ring and
1966
+ * caption, the ledger rules of the description-list total and the table
1967
+ * footer, and the band story's parts. Minima over the page, the surfaces and
1968
+ * a hovered row (translucent fills composited over the surface).
1969
+ */
1970
+ describe('Component pairings of 3.36 (request 056)', () => {
1971
+ const lib = (path: string) => resolve(__dirname, '../lib', path);
1972
+ const sheets = {
1973
+ strip: compile(lib('state-strip/state-strip.component.scss')).css,
1974
+ dl: compile(lib('description-list/description-list.component.scss')).css,
1975
+ table: compile(lib('table/table.component.scss')).css,
1976
+ };
1977
+ const body = (sheet: keyof typeof sheets, selector: string) => {
1978
+ const css = sheets[sheet];
1979
+ const at = css.indexOf(`\n${selector} {`);
1980
+ expect([selector, at > -1]).toEqual([selector, true]);
1981
+ return css.slice(at, css.indexOf('}', at));
1982
+ };
1983
+ const floor = (n: number) => Math.floor(n * 100) / 100;
1984
+ const grounds = ['--color-background', '--color-surface', '--color-surface-2', '--color-surface-hover', '--color-surface-sunken'];
1985
+
1986
+ it('AC-4, AC-12, AC-19: the rules pair these tokens', () => {
1987
+ expect(body('strip', '.lc-state-strip__mark--success')).toContain('background-color: var(--color-text-success)');
1988
+ expect(body('strip', '.lc-state-strip__mark--warning')).toContain('background-color: var(--color-text-warning)');
1989
+ expect(body('strip', '.lc-state-strip__mark--error')).toContain('background-color: var(--color-text-error)');
1990
+ expect(body('strip', '.lc-state-strip__mark--neutral')).toContain('background-color: var(--color-field-border)');
1991
+ expect(body('strip', '.lc-state-strip__cell:focus-visible')).toContain('outline: 2px solid var(--color-primary-500)');
1992
+ const caption = body('strip', '.lc-state-strip__caption');
1993
+ expect(caption).toContain('background-color: var(--color-text-primary)');
1994
+ expect(caption).toContain('color: var(--color-surface)');
1995
+ expect(body('dl', '.lc-dl .lc-dl__row--total')).toContain('border-top: 1px solid var(--color-field-border)');
1996
+ expect(body('table', '.lc-table tfoot tr')).toContain('border-bottom: 3px double var(--color-field-border)');
1997
+ });
1998
+
1999
+ // [light, dark], floored; design 056 §5 quotes the rounded values (warning
2000
+ // 5.48 / 6.35, error 11.44, ring 3.97 / 7.58, caption 17.74 / 15.85).
2001
+ const expected = {
2002
+ success: [4.81, 6.97],
2003
+ warning: [5.47, 6.34],
2004
+ error: [11.43, 7.21],
2005
+ neutral: [4.39, 5.56],
2006
+ ring: [3.96, 7.57],
2007
+ caption: [17.73, 15.85],
2008
+ };
2009
+
2010
+ it.each(palettes.map(([name, tokens], i) => [name, tokens, i] as const))('AC-4, AC-12: %s palette: the design 056 §5 ratios', (_palette, tokens, i) => {
2011
+ const c = (name: string) => color(tokens, name);
2012
+ const surface = c('--color-surface');
2013
+ const ground = (name: string) => over(c(name), surface);
2014
+ const worst = (ink: string) => floor(Math.min(...grounds.map((g) => ratio(over(c(ink), ground(g)), ground(g)))));
2015
+ const got = {
2016
+ success: worst('--color-text-success'),
2017
+ warning: worst('--color-text-warning'),
2018
+ error: worst('--color-text-error'),
2019
+ neutral: worst('--color-field-border'),
2020
+ ring: worst('--color-primary-500'),
2021
+ caption: floor(ratio(c('--color-surface'), c('--color-text-primary'))),
2022
+ };
2023
+ expect(got).toEqual(Object.fromEntries(Object.entries(expected).map(([key, pair]) => [key, pair[i]])));
2024
+ // Marks, ring and rules: 3:1 (WCAG 1.4.11); the caption is text: 4.5:1.
2025
+ for (const key of ['success', 'warning', 'error', 'neutral', 'ring'] as const) expect(got[key]).toBeGreaterThanOrEqual(3);
2026
+ expect(got.caption).toBeGreaterThanOrEqual(4.5);
2027
+ });
2028
+
2029
+ it.each(palettes)('AC-16: %s palette: the band story\'s five part colours keep 3:1 on the surface', (_palette, tokens) => {
2030
+ const surface = color(tokens, '--color-surface');
2031
+ const parts = ['--color-primary-700', '--color-primary-500', '--color-info-default', '--color-secondary-700', '--color-neutral-500'];
2032
+ for (const part of parts) expect([part, ratio(over(color(tokens, part), surface), surface) >= 3]).toEqual([part, true]);
2033
+ });
2034
+ });
@@ -2,7 +2,10 @@ import * as i0 from "@angular/core";
2
2
  export type AlertVariant = 'success' | 'error' | 'warning' | 'info';
3
3
  /**
4
4
  * Layout of the projected actions (`slot="action"`).
5
- * - `inline`: one action right of the body, vertically centred (3.2)
5
+ * - `inline`: one action right of the body, vertically centred (3.2); from
6
+ * 16rem to 30rem inside the border (alerts of 258–481 px at 100 % text,
7
+ * 514–961 px at 200 %) a button action takes its own row below the text,
8
+ * lined up with it, at its natural width (3.35)
6
9
  * - `stacked`: the actions in their own row below the title and message;
7
10
  * below 576 px of alert width one per line at full width, 8 px apart,
8
11
  * from 576 px side by side at their natural width
@@ -25,6 +28,10 @@ export type AlertActionsLayout = 'inline' | 'stacked';
25
28
  * several actions stacked below it (`actionsLayout="stacked"`); 3.12: a text
26
29
  * link (`<a slot="action" href="…">`) as the one way forward gets a
27
30
  * 44 × 44 px target, link colour, an underline and a focus ring
31
+ * - 3.35: from 16rem to 30rem inside the border (alerts of 258–481 px at
32
+ * 100 % text, 514–961 px at 200 %) an inline button action takes its own
33
+ * row below the text, lined up with it, at its natural width, so the
34
+ * message keeps about 200 px beside the ×
28
35
  * - 3.13.1: narrow layout below 16rem inside the border (alerts under 258 px
29
36
  * at 100 % text, 514 px at 200 %): the icon shares the first row with the
30
37
  * title, message and actions take the full width below, the padding is one
@@ -84,7 +91,10 @@ export declare class AlertComponent {
84
91
  iconAriaLabel: import("@angular/core").InputSignal<string | undefined>;
85
92
  /**
86
93
  * Layout of the projected actions (`slot="action"`): `inline` — one action
87
- * right of the body (3.2); `stacked` — the actions in their own row below
94
+ * right of the body (3.2); from 16rem to 30rem inside the border (alerts of
95
+ * 258–481 px at 100 % text, 514–961 px at 200 %) a button action takes its
96
+ * own row below the text, lined up with it, at its natural width (3.35);
97
+ * `stacked` — the actions in their own row below
88
98
  * the title and message, one per line at full width and 8 px apart below
89
99
  * 576 px of alert width, side by side at their natural width from there.
90
100
  * @default 'inline'
@@ -138,7 +138,11 @@ export declare class BottomNavComponent {
138
138
  * destinations' badges; pass the value to show. It sits on the bar's last
139
139
  * entry; values up to three characters (`'99+'`) fit: on the last entry a
140
140
  * badge that would pass the entry's edge moves left until it ends there
141
- * (3.33). `null` or `undefined`: no badge.
141
+ * (3.33). `null` or `undefined`: no badge. Known limit (3.35, 052-n3):
142
+ * '99+' on the last entry of a five-entry bar at 360 px starts 3.2 px
143
+ * (docked) / 3.4 px (floating) left of the pill centre and covers two of the
144
+ * More glyph's three dots; the label and the read badge text still name the
145
+ * entry.
142
146
  *
143
147
  * @default null
144
148
  */
@@ -1,6 +1,6 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ElementRef } from "@angular/core";
3
- export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'danger' | 'warning' | 'info' | 'inverse';
3
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'danger' | 'warning' | 'info' | 'inverse' | 'inverse-outline';
4
4
  export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
5
5
  export type ButtonType = 'button' | 'submit' | 'reset';
6
6
  /**
@@ -15,7 +15,9 @@ export declare class ButtonComponent {
15
15
  * such as the default `lc-hero` bands (primary, neutral, info, accent-purple,
16
16
  * accent-violet). It looks the same in both themes. Not for light backgrounds
17
17
  * or `variant="light"` heroes. CSS hook `--lc-button-inverse-fg` (default
18
- * `#0d353d`) sets its ink; keep 4.5:1 on white.
18
+ * `#0d353d`) sets its ink; keep 4.5:1 on white. `inverse-outline` (3.36) is
19
+ * the quieter action beside it on the same bands: white text and keyline on
20
+ * a dark translucent fill, the same sizes, height, icon slots and states.
19
21
  * @default 'primary'
20
22
  */
21
23
  readonly variant: import("@angular/core").InputSignal<ButtonVariant>;
@@ -239,7 +241,9 @@ export declare class ButtonLinkComponent {
239
241
  * such as the default `lc-hero` bands (primary, neutral, info, accent-purple,
240
242
  * accent-violet). It looks the same in both themes. Not for light backgrounds
241
243
  * or `variant="light"` heroes. CSS hook `--lc-button-inverse-fg` (default
242
- * `#0d353d`) sets its ink; keep 4.5:1 on white.
244
+ * `#0d353d`) sets its ink; keep 4.5:1 on white. `inverse-outline` (3.36) is
245
+ * the quieter link beside it on the same bands: white text and keyline on a
246
+ * dark translucent fill, the same sizes and height.
243
247
  * @default 'primary'
244
248
  */
245
249
  readonly variant: import("@angular/core").InputSignal<ButtonVariant>;
@@ -19,6 +19,8 @@ export type CardFooterLayout = 'default' | 'equal';
19
19
  * the title stays a heading
20
20
  * - Equal footer (3.29): `footerLayout="equal"` gives two answers of equal
21
21
  * weight in the `[card-footer]` slot the same width
22
+ * - Collapsible (3.36): `collapsible` with `[(expanded)]` turns the head into
23
+ * one toggle that opens and closes the body and footer
22
24
  *
23
25
  * @example
24
26
  * ```html
@@ -181,23 +183,58 @@ export declare class CardComponent {
181
183
  * @default 'default'
182
184
  */
183
185
  footerLayout: import("@angular/core").InputSignal<CardFooterLayout>;
186
+ /**
187
+ * The card can close (3.36, regeln row 1): the head (icon, title, subtitle)
188
+ * becomes one toggle, a button inside the title's heading with
189
+ * `aria-expanded` and `aria-controls`, whose hit area is the whole head (at
190
+ * least 44 px high); Enter, Space or a click open and close the body and the
191
+ * `[card-footer]`. A chevron at the end of the head shows the state.
192
+ * `[card-header-action]` stays a separate control beside it. Needs the
193
+ * built-in `title` (otherwise ignored); wins over `clickable`, `href` and
194
+ * `route`, which are then ignored; ignored on `dashed`. Bind `[(expanded)]`.
195
+ * @default false
196
+ */
197
+ collapsible: import("@angular/core").InputSignal<boolean>;
198
+ /**
199
+ * Open state of a `collapsible` card (3.36), two-way: `[(expanded)]`. Open
200
+ * by default, so a plain `collapsible` hides nothing; set `false` for a card
201
+ * that starts closed (also in prerendered HTML). Closed, only the head
202
+ * shows: the body is `hidden` and the footer is not displayed.
203
+ * @default true
204
+ */
205
+ readonly expanded: import("@angular/core").ModelSignal<boolean>;
184
206
  /**
185
207
  * Emitted when a `clickable` card is clicked or activated with Enter or
186
208
  * Space. Not emitted on a link card (`href`/`route`): the link navigates by
187
209
  * itself (one navigation); bind the native `(click)` for analytics.
188
210
  */
189
211
  readonly cardClick: import("@angular/core").OutputEmitterRef<MouseEvent>;
212
+ /** Generated id prefix (3.36): creation order, so prerendered HTML is the same in every build. */
213
+ private readonly uid;
214
+ /** Id of the body a collapsible card's toggle controls. */
215
+ protected readonly bodyId: string;
216
+ /** Id of the subtitle that describes a collapsible card's toggle. */
217
+ protected readonly subtitleId: string;
218
+ /**
219
+ * Whether the card collapses (3.36): `collapsible` with a built-in title,
220
+ * except for the dashed tile.
221
+ */
222
+ protected readonly isCollapsible: import("@angular/core").Signal<boolean>;
190
223
  /**
191
224
  * How the card links (3.24): `href` wins over `route`; `''`/`null` routes
192
- * are no link, and the dashed "coming soon" tile never links.
225
+ * are no link, and the dashed "coming soon" tile never links. A
226
+ * collapsible card (3.36) never links.
193
227
  */
194
228
  protected readonly linkMode: import("@angular/core").Signal<"href" | "route" | null>;
195
229
  /**
196
230
  * Whether the card acts as a button: `clickable`, except for the dashed
197
- * "coming soon" tile, which is never interactive, and a link card, whose
198
- * anchor is the only interactive element.
231
+ * "coming soon" tile, which is never interactive, a link card, whose
232
+ * anchor is the only interactive element, and a collapsible card (3.36),
233
+ * whose toggle is.
199
234
  */
200
235
  protected isClickable: import("@angular/core").Signal<boolean>;
236
+ /** Opens or closes a collapsible card; `expandedChange` emits the new state. */
237
+ protected toggle(): void;
201
238
  /** `ariaLabel` on the card; on a link card it names the anchor instead. */
202
239
  protected readonly cardAriaLabel: import("@angular/core").Signal<string | undefined>;
203
240
  constructor();
@@ -309,7 +346,18 @@ export declare class CardComponent {
309
346
  "required": false;
310
347
  "isSignal": true;
311
348
  };
349
+ "collapsible": {
350
+ "alias": "collapsible";
351
+ "required": false;
352
+ "isSignal": true;
353
+ };
354
+ "expanded": {
355
+ "alias": "expanded";
356
+ "required": false;
357
+ "isSignal": true;
358
+ };
312
359
  }, {
360
+ "expanded": "expandedChange";
313
361
  "cardClick": "cardClick";
314
362
  }, never, ["[card-badge]", "[card-header-action]", "[card-header]", "*", "[card-footer]"], true, never>;
315
363
  }
@@ -25,6 +25,10 @@ export type ComboboxSize = 'xs' | 'sm' | 'md' | 'lg';
25
25
  * (`[(selectedValue)]`, single mode), next to the `valueChange` output with
26
26
  * the option object and the forms binding.
27
27
  *
28
+ * `focus()` (3.35) moves the focus into the field's `<input>` when a form
29
+ * opens or after a rejected save (WCAG 2.4.3); it opens the list as a user's
30
+ * focus does (`openOnFocus`).
31
+ *
28
32
  * @example
29
33
  * ```html
30
34
  * <lc-combobox
@@ -62,6 +66,16 @@ export declare class ComboboxComponent implements ControlValueAccessor {
62
66
  readonly helperText: import("@angular/core").InputSignal<string | undefined>;
63
67
  /** Error message */
64
68
  readonly error: import("@angular/core").InputSignal<string | undefined>;
69
+ /**
70
+ * Announce the error message the moment it appears (`role="alert"` on the
71
+ * message), as `lc-textarea`. Set `false` when an `lc-alert` error summary
72
+ * already announces the errors, e.g. a page that moves focus to this field
73
+ * after a failed submit (422): the error is then read once, with the field,
74
+ * not twice (alert and description). The message stays visible, stays linked
75
+ * through `aria-describedby`, and the input keeps `aria-invalid`.
76
+ * @default true
77
+ */
78
+ readonly announceError: import("@angular/core").InputSignal<boolean>;
65
79
  /** Disabled state */
66
80
  readonly disabled: import("@angular/core").InputSignal<boolean>;
67
81
  /** Allow multiple selections */
@@ -176,6 +190,23 @@ export declare class ComboboxComponent implements ControlValueAccessor {
176
190
  registerOnChange(fn: (value: ComboboxValue) => void): void;
177
191
  registerOnTouched(fn: () => void): void;
178
192
  setDisabledState(isDisabled: boolean): void;
193
+ /**
194
+ * Moves the focus into the field's `<input>` (3.35), e.g. when a form opens
195
+ * with this field first, or to the field with the error after a rejected
196
+ * save (422) (WCAG 2.4.3). `options` as for `HTMLElement.focus()`
197
+ * (`{ preventScroll: true }`). Shows the same focus ring as a user's focus.
198
+ * Like a user's focus it opens the list when `openOnFocus` is `true` and
199
+ * there is something to show; with `false` the list stays closed
200
+ * (`aria-expanded="false"`). A disabled field takes no focus (it stays where
201
+ * it is). Call it once the field has rendered, e.g.
202
+ * `afterNextRender(() => this.month().focus(), { injector })` with
203
+ * `month = viewChild.required(ComboboxComponent)` and the page component's
204
+ * `injector = inject(Injector)`. In an `lc-modal` that opens with the field,
205
+ * that call runs after the dialog moved the focus to its first control.
206
+ * Does nothing before the component has rendered.
207
+ * @param options Passed to the native `focus()`.
208
+ */
209
+ focus(options?: FocusOptions): void;
179
210
  /**
180
211
  * Clear the current selection and query. Emits `null` (single) or `[]`
181
212
  * (multiple) exactly once, like a user-initiated deselect.
@@ -247,6 +278,11 @@ export declare class ComboboxComponent implements ControlValueAccessor {
247
278
  "required": false;
248
279
  "isSignal": true;
249
280
  };
281
+ "announceError": {
282
+ "alias": "announceError";
283
+ "required": false;
284
+ "isSignal": true;
285
+ };
250
286
  "disabled": {
251
287
  "alias": "disabled";
252
288
  "required": false;
@@ -1,3 +1,4 @@
1
+ import { BadgeVariant } from "@lifecorner/angular-ui-kit/badge";
1
2
  import * as i0 from "@angular/core";
2
3
  /**
3
4
  * Emphasis treatment applied to a row's value.
@@ -26,6 +27,24 @@ export interface DescriptionListItem {
26
27
  datetime?: string;
27
28
  /** Optional visual emphasis for the value. @default 'default' */
28
29
  emphasis?: DescriptionListEmphasis;
30
+ /**
31
+ * Shows the value as an `lc-badge` (3.36, quellen row 2): rounded, `sm` in
32
+ * an md list, `xs` in an sm list. A `value` that is empty or the same word
33
+ * (trimmed, any case) is not shown; another value stays as text before the
34
+ * badge („1“ [Freigegeben]), and `href` or `datetime` wrap that text only.
35
+ * Term and value stay one `dt`/`dd` pair, read as „term, badge“.
36
+ */
37
+ badge?: string;
38
+ /** Variant of the `badge`, as `lc-badge`. @default 'default' */
39
+ badgeVariant?: BadgeVariant;
40
+ /**
41
+ * The sum item (3.36, app-shell row 2): a rule above and a double rule
42
+ * below (`--color-field-border`), term and value semibold with tabular
43
+ * figures; its `emphasis` is ignored. `totalAriaLabel` is read before its
44
+ * term. Usually the last item.
45
+ * @default false
46
+ */
47
+ total?: boolean;
29
48
  /** Optional opaque payload echoed back in `itemClick`. */
30
49
  id?: string;
31
50
  }
@@ -55,6 +74,8 @@ export type DescriptionListSeparator = 'line' | 'divider' | 'none';
55
74
  * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`
56
75
  * rows hold a native button (named by term and value) that the keyboard
57
76
  * reaches, and the `dl` keeps its term and value pairs.
77
+ * - 3.36: an item's value as a badge (`badge`, `badgeVariant`), and a sum
78
+ * item (`total`) with ledger rules, announced with `totalAriaLabel`.
58
79
  *
59
80
  * @example
60
81
  * ```html
@@ -99,8 +120,22 @@ export declare class DescriptionListComponent {
99
120
  * @default false
100
121
  */
101
122
  readonly clickable: import("@angular/core").InputSignal<boolean>;
123
+ /**
124
+ * Read before the term of a `total` item and not shown (3.36, app-shell
125
+ * row 2): „Summe“ makes a screen reader read „Summe, Gesamtkosten Ist,
126
+ * 522.530,00 €“. Empty adds nothing.
127
+ * @default ''
128
+ */
129
+ readonly totalAriaLabel: import("@angular/core").InputSignal<string>;
102
130
  /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */
103
131
  readonly itemClick: import("@angular/core").OutputEmitterRef<DescriptionListItem>;
132
+ /** The visually hidden start of a total's term: „Summe, “ (3.36). */
133
+ protected readonly totalPrefix: import("@angular/core").Signal<string>;
134
+ /**
135
+ * Whether a badge item also shows its `value` as text (3.36, D-2): not when
136
+ * it is empty or the badge's word.
137
+ */
138
+ protected badgeShowsValue(item: DescriptionListItem): boolean;
104
139
  /** Generated id prefix, taken once per instance, so later ids don't shift. */
105
140
  private readonly uid;
106
141
  /** Id of row `index`'s term (only rendered when `clickable`). */
@@ -147,6 +182,11 @@ export declare class DescriptionListComponent {
147
182
  "required": false;
148
183
  "isSignal": true;
149
184
  };
185
+ "totalAriaLabel": {
186
+ "alias": "totalAriaLabel";
187
+ "required": false;
188
+ "isSignal": true;
189
+ };
150
190
  }, {
151
191
  "itemClick": "itemClick";
152
192
  }, never, never, true, never>;
@@ -12,7 +12,10 @@ export type DrawerSize = 'sm' | 'md' | 'lg' | 'xl';
12
12
  * - Close on Escape key support
13
13
  * - Heading text display
14
14
  * - Content projection for arbitrary body content
15
- * - Accessible with ARIA dialog role, focus trap and focus restore
15
+ * - Accessible with ARIA dialog role, focus trap and focus restore;
16
+ * `returnFocusTo` (3.35) names the element that gets the focus back after
17
+ * the drawer closed (WebKit doesn't focus a tapped button, so the one
18
+ * captured at open is `body`)
16
19
  *
17
20
  * @example
18
21
  * ```html
@@ -42,6 +45,20 @@ export declare class DrawerComponent implements OnDestroy {
42
45
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
43
46
  /** Accessible name of the close button. */
44
47
  readonly closeAriaLabel: import("@angular/core").InputSignal<string>;
48
+ /**
49
+ * Element that gets the focus back after the drawer closed (3.35), instead
50
+ * of the one that had it at open. Set it to the trigger: WebKit (Safari,
51
+ * every iOS browser) doesn't focus a tapped button, so the captured element
52
+ * is `body`. Applied when the panel has left the DOM after its 250 ms
53
+ * slide-out, once the focus trap has restored its captured element. Holds
54
+ * on ×, Escape, backdrop, `open` false, and when the drawer is destroyed
55
+ * while open. A custom-element host that can't take focus (`<lc-button>`)
56
+ * hands it to its first focusable descendant. Ignored when the element is
57
+ * no longer in the document or can't take focus; the captured element then
58
+ * keeps it. `null`: as before.
59
+ * @default null
60
+ */
61
+ readonly returnFocusTo: import("@angular/core").InputSignal<HTMLElement | null>;
45
62
  /** Emitted when the drawer should close. */
46
63
  readonly closed: import("@angular/core").OutputEmitterRef<void>;
47
64
  /** Internal visibility — stays true during the slide-out animation. */
@@ -54,8 +71,22 @@ export declare class DrawerComponent implements OnDestroy {
54
71
  private readonly drawerId;
55
72
  private closeTimer?;
56
73
  private originalOverflow?;
74
+ /**
75
+ * The environment injector: a node injector would cancel the focus-return
76
+ * hook when the drawer is destroyed while open.
77
+ */
78
+ private readonly environmentInjector;
79
+ private destroyed;
57
80
  constructor();
58
81
  ngOnDestroy(): void;
82
+ /**
83
+ * Focuses `returnFocusTo` after the next render (3.35): the template
84
+ * refresh has removed the panel, and `cdkTrapFocus` has restored its
85
+ * captured element on destroy, so this is the last focus write. Reads the
86
+ * input again then, so a trigger re-rendered meanwhile is taken from a
87
+ * fresh query. Does nothing when the drawer opened again meanwhile.
88
+ */
89
+ private scheduleFocusReturn;
59
90
  /**
60
91
  * Handle backdrop click. Ignored while another overlay (menu, popover, …)
61
92
  * sits above this drawer — the click belongs to that layer.
@@ -118,6 +149,11 @@ export declare class DrawerComponent implements OnDestroy {
118
149
  "required": false;
119
150
  "isSignal": true;
120
151
  };
152
+ "returnFocusTo": {
153
+ "alias": "returnFocusTo";
154
+ "required": false;
155
+ "isSignal": true;
156
+ };
121
157
  }, {
122
158
  "closed": "closed";
123
159
  }, never, ["*"], true, never>;
@@ -23,7 +23,9 @@ export interface GalleryItem {
23
23
  * - Configurable custom column count
24
24
  * - Lightbox overlay with navigation (prev/next)
25
25
  * - Keyboard navigation (Arrow keys, Escape); focus is trapped inside the
26
- * lightbox and returned to the originating thumbnail on close
26
+ * lightbox and returned to the originating thumbnail on close: the clicked
27
+ * or tapped thumbnail, also in WebKit, where a tap doesn't focus it, so the
28
+ * gallery needs no `returnFocusTo` (3.35)
27
29
  * - Optional captions and category filtering
28
30
  * - Lazy loading with placeholder shimmer
29
31
  * - Zoom control in lightbox