@lifecorner/angular-ui-kit 3.25.1 → 3.27.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.
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +18 -2
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +47 -9
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +8 -4
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_reset.scss +4 -2
- package/src/styles/focus-indicators.spec.ts +199 -0
- package/types/lifecorner-angular-ui-kit-button.d.ts +20 -0
- package/types/lifecorner-angular-ui-kit-dependency-viewer.d.ts +33 -4
- package/types/lifecorner-angular-ui-kit-description-list.d.ts +4 -0
- package/types/lifecorner-angular-ui-kit-file-upload.d.ts +11 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-log-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n DestroyRef,\n ElementRef,\n Injector,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_ARROW_DOWN,\n ICON_CLIPBOARD,\n ICON_PLAYER_PAUSE,\n ICON_PLAYER_PLAY,\n ICON_SEARCH,\n ICON_TRASH,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\nexport type LogViewerVariant = 'terminal' | 'log';\n\nexport interface LogLine {\n text: string;\n level?: LogLevel;\n timestamp?: Date;\n source?: string;\n meta?: Record<string, unknown>;\n}\n\n/** SGR code → CSS class for the ANSI subset the viewer renders. */\nconst ANSI_CLASSES: Record<string, string> = {\n '1': 'ansi-bold',\n '3': 'ansi-italic',\n '30': 'ansi-black',\n '31': 'ansi-red',\n '32': 'ansi-green',\n '33': 'ansi-yellow',\n '34': 'ansi-blue',\n '35': 'ansi-magenta',\n '36': 'ansi-cyan',\n '37': 'ansi-white',\n '90': 'ansi-gray',\n '91': 'ansi-bright-red',\n '92': 'ansi-bright-green',\n '93': 'ansi-bright-yellow',\n '94': 'ansi-bright-blue',\n '95': 'ansi-bright-magenta',\n '96': 'ansi-bright-cyan',\n '97': 'ansi-bright-white',\n};\n\n/** Row height fallback when `--lc-log-viewer-line-height` can't be read (SSR, tests). */\nconst DEFAULT_LINE_HEIGHT = 22;\n\n/** Distance from the bottom (px) still counted as \"at the bottom\". */\nconst AT_BOTTOM_THRESHOLD = 30;\n\n/**\n * Streaming log / terminal viewer component.\n *\n * Supports controlled (lines input) and streaming (stream$ observable) modes,\n * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,\n * and filtering.\n *\n * The virtual window is sized from the *measured* height of the scroll area\n * (ResizeObserver), so any `height` — `400px`, `50vh`, `100%` inside a flex\n * parent — renders a full window. Row height comes from the CSS custom\n * property `--lc-log-viewer-line-height` (default 22px), read once after the\n * first render; override it on the host to change the row density.\n *\n * @example\n * ```html\n * <lc-log-viewer [stream$]=\"logs$\" autoScroll height=\"600px\" variant=\"terminal\" />\n * ```\n */\n@Component({\n selector: 'lc-log-viewer',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_TRASH, ICON_SEARCH, ICON_PLAYER_PLAY, ICON_PLAYER_PAUSE, ICON_CLIPBOARD, ICON_ARROW_DOWN]),\n ],\n templateUrl: './log-viewer.component.html',\n styleUrls: ['./log-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class LogViewerComponent {\n private static nextId = 0;\n /** Id of the search input (links its visually-hidden label). */\n protected readonly searchInputId = `lc-log-viewer-search-${++LogViewerComponent.nextId}`;\n\n private readonly ngZone = inject(NgZone);\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n private readonly scrollContainer = viewChild.required<ElementRef<HTMLElement>>('scrollContainer');\n private readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');\n private streamSub?: Subscription;\n\n /** Controlled mode: array of log lines */\n readonly lines = input<LogLine[]>([]);\n\n /** Streaming mode: observable of log lines */\n readonly stream$ = input<Observable<LogLine | LogLine[]>>();\n\n /** Maximum lines to keep in buffer */\n readonly maxLines = input<number>(10_000);\n\n /**\n * Follow new lines: scroll to the bottom whenever the visible line count\n * changes — but only while the reader is already at the bottom, so scrolling\n * up to inspect older output is never fought.\n */\n readonly autoScroll = input<boolean>(true);\n\n /** Show timestamps column */\n readonly showTimestamps = input<boolean>(true);\n\n /** Show line numbers */\n readonly showLineNumbers = input<boolean>(false);\n\n /** Parse ANSI color codes */\n readonly ansiColors = input<boolean>(true);\n\n /** Filter by log levels */\n readonly levelFilter = input<LogLevel[]>();\n\n /** Search query to highlight */\n readonly searchQuery = input<string>();\n\n /** Container height */\n readonly height = input<string>('400px');\n\n /** Visual variant */\n readonly variant = input<LogViewerVariant>('log');\n\n /**\n * Render lines as interactive rows (`role=\"button\"`, focusable, Enter/Space\n * emit `lineClick`). Set this whenever you bind `(lineClick)` so keyboard\n * users can activate a line too; a plain click emits either way.\n */\n readonly clickableLines = input<boolean>(false);\n\n /** BCP 47 locale of the line timestamps (24-hour clock). */\n readonly locale = input('en-US');\n\n /** Tooltip of the pause button while streaming. */\n readonly pauseLabel = input('Pause');\n\n /** Tooltip of the pause button while paused. */\n readonly resumeLabel = input('Resume');\n\n /** Accessible name of the pause button while streaming. */\n readonly pauseAriaLabel = input('Pause stream');\n\n /** Accessible name of the pause button while paused. */\n readonly resumeAriaLabel = input('Resume stream');\n\n /** Tooltip of the clear button. */\n readonly clearLabel = input('Clear');\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear log');\n\n /** Tooltip of the copy button. */\n readonly copyLabel = input('Copy all');\n\n /** Accessible name of the copy button. */\n readonly copyAriaLabel = input('Copy all lines');\n\n /** Tooltip of the search toggle. */\n readonly searchLabel = input('Search (/)');\n\n /** Accessible name of the search toggle. */\n readonly searchAriaLabel = input('Search log');\n\n /** Label of the search field (visually hidden). */\n readonly searchInputLabel = input('Search logs');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search logs...');\n\n /** Unit after the error count in the level filter. */\n readonly errorsLabel = input('errors');\n\n /** Unit after the warning count in the level filter. */\n readonly warningsLabel = input('warnings');\n\n /** Unit after the line count. */\n readonly linesLabel = input('lines');\n\n /** Accessible name of the log region. */\n readonly outputAriaLabel = input('Log output');\n\n /** Label of the jump-to-bottom button. */\n readonly jumpToBottomLabel = input('Jump to bottom');\n\n /** Emitted when a line is clicked (or activated with Enter/Space) */\n readonly lineClick = output<LogLine>();\n\n /**\n * Emitted with the copied text after \"Copy all\" — also when the Clipboard\n * API is unavailable (insecure context, denied permission), so consumers can\n * offer their own fallback.\n */\n readonly copyAll = output<string>();\n\n /** Emitted when scroll state changes */\n readonly scrollStateChange = output<{ atBottom: boolean }>();\n\n /** Internal buffer of all lines */\n protected buffer = signal<LogLine[]>([]);\n\n /** Whether stream is paused */\n protected paused = signal(false);\n\n /** Whether user is at bottom of scroll */\n protected atBottom = signal(true);\n\n /** Internal search input */\n protected internalSearch = signal('');\n\n /** Internal level filter */\n protected internalLevelFilter = signal<Set<LogLevel>>(new Set());\n\n /** Show search bar */\n protected showSearch = signal(false);\n\n /** Effective search query */\n protected effectiveSearch = computed(() => {\n return this.searchQuery() || this.internalSearch();\n });\n\n /** Filtered lines for display */\n protected filteredLines = computed(() => {\n let lines = this.buffer();\n const levels = this.levelFilter();\n const internalFilter = this.internalLevelFilter();\n\n // Apply level filter\n if (levels?.length) {\n lines = lines.filter((l) => l.level && levels.includes(l.level));\n } else if (internalFilter.size > 0) {\n lines = lines.filter((l) => l.level && internalFilter.has(l.level));\n }\n\n return lines;\n });\n\n // ── Virtual window ────────────────────────────────────────────────────\n\n protected scrollTop = signal(0);\n\n /**\n * Measured `clientHeight` of the scroll area (excludes toolbar and search\n * bar by construction). 0 until the first ResizeObserver callback.\n */\n private readonly viewportHeight = signal(0);\n\n /** Row height in px — `--lc-log-viewer-line-height`, read once after render. */\n protected readonly lineHeight = signal(DEFAULT_LINE_HEIGHT);\n\n protected visibleRange = computed(() => {\n const lineHeight = this.lineHeight();\n const measured = this.viewportHeight();\n // Before the first measurement fall back to the declared height (minus a\n // nominal toolbar) so the first frame already shows a plausible window.\n const viewHeight = measured > 0 ? measured : (parseInt(this.height(), 10) || 400) - 40;\n const total = this.filteredLines().length;\n const start = Math.floor(this.scrollTop() / lineHeight);\n const visible = Math.ceil(viewHeight / lineHeight) + 2;\n return {\n start: Math.max(0, start - 1),\n end: Math.min(total, start + visible + 1),\n total,\n totalHeight: total * lineHeight,\n };\n });\n\n protected visibleLines = computed(() => {\n const range = this.visibleRange();\n return this.filteredLines().slice(range.start, range.end).map((line, i) => ({\n ...line,\n _index: range.start + i,\n }));\n });\n\n protected containerClasses = computed(() => {\n return `lc-log-viewer lc-log-viewer--${this.variant()}`;\n });\n\n /** Line counts per level for toolbar */\n protected levelCounts = computed(() => {\n const buf = this.buffer();\n const counts = { debug: 0, info: 0, warn: 0, error: 0 };\n for (const line of buf) {\n if (line.level && line.level in counts) counts[line.level as keyof typeof counts]++;\n }\n return counts;\n });\n\n constructor() {\n // Controlled mode: mirror `lines` into the buffer — unconditionally, so\n // `[lines]=\"[]\"` clears the view. `maxLines` is read untracked: changing\n // it must not wipe a streamed buffer through this effect.\n effect(() => {\n const input = this.lines();\n untracked(() => this.buffer.set(this.trim(input, this.maxLines())));\n });\n\n // Streaming mode. The subscribe body runs untracked: BehaviorSubject /\n // ReplaySubject / of() emit synchronously inside this effect, and reading\n // `paused()` / `maxLines()` there would make them dependencies — toggling\n // Pause would then re-subscribe and replay duplicates.\n effect(() => {\n const stream = this.stream$();\n this.streamSub?.unsubscribe();\n this.streamSub = undefined;\n if (!stream) return;\n untracked(() => {\n this.streamSub = stream.subscribe((data) => this.append(Array.isArray(data) ? data : [data]));\n });\n });\n this.destroyRef.onDestroy(() => this.streamSub?.unsubscribe());\n\n // Follow mode: whenever the visible lines change (new lines, filter),\n // stick to the bottom — but only if the reader is already there.\n effect(() => {\n this.filteredLines();\n if (!this.autoScroll() || !untracked(this.atBottom)) return;\n afterNextRender(() => this.scrollToBottom(), { injector: this.injector });\n });\n\n afterNextRender(() => {\n const el = this.scrollContainer().nativeElement;\n\n // Row height from CSS (once) so the virtual window matches the rows.\n const declared = parseFloat(getComputedStyle(el).getPropertyValue('--lc-log-viewer-line-height'));\n if (declared > 0) this.lineHeight.set(declared);\n\n // Passive scroll tracking outside the zone.\n const onScroll = () => this.onScroll();\n this.ngZone.runOutsideAngular(() => el.addEventListener('scroll', onScroll, { passive: true }));\n this.destroyRef.onDestroy(() => el.removeEventListener('scroll', onScroll));\n\n // Measure the scroll area so `height=\"100%\"`, `50vh`, flex parents … all\n // yield a correctly sized window (not one derived from parseInt(height)).\n this.viewportHeight.set(el.clientHeight);\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => this.viewportHeight.set(el.clientHeight));\n observer.observe(el);\n this.destroyRef.onDestroy(() => observer.disconnect());\n });\n }\n\n private trim(lines: LogLine[], max: number): LogLine[] {\n return lines.length > max ? lines.slice(-max) : lines;\n }\n\n /** Append streamed lines to the buffer (no-op while paused). */\n private append(newLines: LogLine[]): void {\n if (untracked(this.paused)) return;\n const max = untracked(this.maxLines);\n this.buffer.update((buf) => this.trim([...buf, ...newLines], max));\n }\n\n protected onScroll(): void {\n const el = this.scrollContainer().nativeElement;\n this.scrollTop.set(el.scrollTop);\n const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < AT_BOTTOM_THRESHOLD;\n if (this.atBottom() !== isAtBottom) {\n this.ngZone.run(() => {\n this.atBottom.set(isAtBottom);\n this.scrollStateChange.emit({ atBottom: isAtBottom });\n });\n }\n }\n\n protected scrollToBottom(): void {\n const el = this.scrollContainer().nativeElement;\n el.scrollTop = el.scrollHeight;\n this.atBottom.set(true);\n }\n\n protected togglePause(): void {\n this.paused.update((v) => !v);\n }\n\n protected clearBuffer(): void {\n this.buffer.set([]);\n }\n\n protected async onCopyAll(): Promise<void> {\n const text = this.filteredLines()\n .map((l) => {\n const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';\n const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';\n return `${ts}${lvl}${l.text}`;\n })\n .join('\\n');\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n // Clipboard API unavailable (insecure context / permission denied):\n // still emit so the consumer can fall back to its own copy path.\n }\n this.copyAll.emit(text);\n }\n\n protected toggleSearch(): void {\n this.showSearch.update((v) => !v);\n if (this.showSearch()) {\n // The input is `@if`-rendered — focus it once it exists (`autofocus`\n // is unreliable for dynamically inserted elements).\n afterNextRender(() => this.searchInput()?.nativeElement.focus(), { injector: this.injector });\n } else {\n this.internalSearch.set('');\n }\n }\n\n protected toggleLevelFilter(level: LogLevel): void {\n this.internalLevelFilter.update((set) => {\n const newSet = new Set(set);\n if (newSet.has(level)) {\n newSet.delete(level);\n } else {\n newSet.add(level);\n }\n return newSet;\n });\n }\n\n protected onLineClick(line: LogLine): void {\n this.lineClick.emit(line);\n }\n\n /**\n * Enter/Space activate an interactive line (see `clickableLines`). Like a\n * native button, Enter fires on keydown and Space on keyup (keydown only\n * prevents the page scroll) — so a consumer that moves focus in its\n * `lineClick` handler doesn't get a stray Space keyup on the new target.\n */\n protected onLineKeydown(event: KeyboardEvent, line: LogLine): void {\n if (!this.clickableLines()) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this.lineClick.emit(line);\n } else if (event.key === ' ') {\n event.preventDefault();\n }\n }\n\n protected onLineKeyup(event: KeyboardEvent, line: LogLine): void {\n if (this.clickableLines() && event.key === ' ') {\n event.preventDefault();\n this.lineClick.emit(line);\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.showSearch()) {\n event.preventDefault();\n this.toggleSearch();\n }\n if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {\n this.scrollContainer().nativeElement.scrollTo(0, 0);\n }\n if (event.key === 'G' && !this.showSearch()) {\n this.scrollToBottom();\n }\n }\n\n protected formatTimestamp(date?: Date): string {\n if (!date) return '';\n return date.toLocaleTimeString(this.locale(), { hour12: false } as Intl.DateTimeFormatOptions);\n }\n\n /**\n * Render a line's text as HTML: ANSI SGR codes become `<span class=\"ansi-…\">`\n * wrappers, everything else is escaped, and search matches are wrapped in\n * `<mark>`. Highlighting is applied per *text* segment — before the spans\n * are assembled — so a query can never match inside a tag or an entity\n * (`e` used to hit `class=\"ansi-red\"`, `&` / `<` never matched at all).\n */\n protected renderLine(text: string): string {\n const query = this.effectiveSearch();\n const matcher = query\n ? new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi')\n : null;\n // Split the raw text on the query (capturing group → odd parts are the\n // matches), escape every part, wrap the matches.\n const renderText = (segment: string): string =>\n matcher\n ? segment\n .split(matcher)\n .map((part, i) =>\n i % 2 === 1\n ? `<mark class=\"lc-log-viewer__match\">${this.escapeHtml(part)}</mark>`\n : this.escapeHtml(part),\n )\n .join('')\n : this.escapeHtml(segment);\n\n if (!this.ansiColors()) return renderText(text);\n\n let html = '';\n let openSpans = 0;\n let cursor = 0;\n // SGR sequences: ESC [ <params> m — params are `;`-separated (may be empty = reset).\n // eslint-disable-next-line no-control-regex -- matching the ESC byte is the point here\n const re = /\\x1b\\[([\\d;]*)m/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n html += renderText(text.slice(cursor, m.index));\n cursor = m.index + m[0].length;\n for (const code of (m[1] || '0').split(';')) {\n if (code === '0' || code === '') {\n // Reset closes everything that is open.\n html += '</span>'.repeat(openSpans);\n openSpans = 0;\n } else if (ANSI_CLASSES[code]) {\n html += `<span class=\"${ANSI_CLASSES[code]}\">`;\n openSpans++;\n }\n // Unrecognised codes are stripped.\n }\n }\n html += renderText(text.slice(cursor));\n return html + '</span>'.repeat(openSpans);\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLInputElement).value;\n }\n}\n","<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n"],"mappings":";;;;AAyCA,MAAM,eAAuC;CAC3C,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;AACP;AAGD,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAgC5B,IAAa,qBAAb,MAAa,mBAAkB;CACrB,OAAO,SAAS;CAEL,gBAAgB,wBAAwB,EAAE,mBAAmB;CAE/D,SAAS,OAAO,MAAM;CACtB,WAAW,OAAO,QAAQ;CAC1B,aAAa,OAAO,UAAU;CAC9B,kBAAkB,UAAU,SAAkC,mBAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAChF,cAAc,UAAwC,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC7E;CAGC,QAAQ,MAAiB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAG5B,UAAU,MAAwC,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGlD,WAAW,MAAc,KAAO,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAOhC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,iBAAiB,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,kBAAkB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAmB,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,SAAS,MAAc,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,UAAU,MAAwB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAOxC,iBAAiB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,SAAS,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,iBAAiB,MAAM,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,kBAAkB,MAAM,iBAAgB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,iBAAiB,MAAM,aAAY,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGnC,YAAY,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,cAAc,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,mBAAmB,MAAM,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,YAAY,OAAM;CAOlB,UAAU,OAAM;CAGhB,oBAAoB,OAAM;CAGzB,SAAS,OAAkB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,SAAS,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGtB,WAAW,OAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,iBAAiB,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,sBAAsB,uBAAsB,IAAI,IAAG,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtD,aAAa,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG1B,kBAAkB,eAAc;EACxC,OAAO,KAAK,YAAW,KAAM,KAAK,eAAc;CAClD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,gBAAgB,eAAc;EACtC,IAAI,QAAQ,KAAK,OAAM;EACvB,MAAM,SAAS,KAAK,YAAW;EAC/B,MAAM,iBAAiB,KAAK,oBAAmB;EAG/C,IAAI,QAAQ,QACV,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,OAAO,SAAS,EAAE,KAAK,CAAC;OAC1D,IAAI,eAAe,OAAO,GAC/B,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,eAAe,IAAI,EAAE,KAAK,CAAC;EAGpE,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAIQ,YAAY,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAMd,iBAAiB,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,aAAa,OAAO,qBAAoB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEjD,eAAe,eAAc;EACrC,MAAM,aAAa,KAAK,WAAU;EAClC,MAAM,WAAW,KAAK,eAAc;EAGpC,MAAM,aAAa,WAAW,IAAI,YAAY,SAAS,KAAK,OAAM,GAAI,EAAE,KAAK,OAAO;EACpF,MAAM,QAAQ,KAAK,cAAa,CAAE,CAAC;EACnC,MAAM,QAAQ,KAAK,MAAM,KAAK,UAAS,IAAK,UAAU;EACtD,MAAM,UAAU,KAAK,KAAK,aAAa,UAAU,IAAI;EACrD,OAAO;GACL,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC;GAC5B,KAAK,KAAK,IAAI,OAAO,QAAQ,UAAU,CAAC;GACxC;GACA,aAAa,QAAQ;EACtB;CACH,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,eAAe,eAAc;EACrC,MAAM,QAAQ,KAAK,aAAY;EAC/B,OAAO,KAAK,cAAa,CAAE,CAAC,MAAM,MAAM,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO;GAC1E,GAAG;GACH,QAAQ,MAAM,QAAQ;EACvB,EAAC;CACJ,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,mBAAmB,eAAc;EACzC,OAAO,gCAAgC,KAAK,QAAO;CACrD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,cAAc,eAAc;EACpC,MAAM,MAAM,KAAK,OAAM;EACvB,MAAM,SAAS;GAAE,OAAO;GAAG,MAAM;GAAG,MAAM;GAAG,OAAO;EAAC;EACrD,KAAK,MAAM,QAAQ,KACjB,IAAI,KAAK,SAAS,KAAK,SAAS,QAAQ,OAAO,KAAK,MAA6B;EAEnF,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EAIE,aAAY;GACV,MAAM,QAAQ,KAAK,MAAK;GACxB,gBAAgB,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,SAAQ,CAAE,CAAC,CAAC;EACpE,CAAC;EAMD,aAAY;GACV,MAAM,SAAS,KAAK,QAAO;GAC3B,KAAK,WAAW,YAAW;GAC3B,KAAK,YAAY;GACjB,IAAI,CAAC,QAAQ;GACb,gBAAe;IACb,KAAK,YAAY,OAAO,WAAW,SAAS,KAAK,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;GAC9F,CAAC;EACH,CAAC;EACD,KAAK,WAAW,gBAAgB,KAAK,WAAW,YAAW,CAAE;EAI7D,aAAY;GACV,KAAK,cAAa;GAClB,IAAI,CAAC,KAAK,WAAU,KAAM,CAAC,UAAU,KAAK,QAAQ,GAAG;GACrD,sBAAsB,KAAK,eAAc,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;EAC1E,CAAC;EAED,sBAAqB;GACnB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;GAGlC,MAAM,WAAW,WAAW,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,6BAA6B,CAAC;GAChG,IAAI,WAAW,GAAG,KAAK,WAAW,IAAI,QAAQ;GAG9C,MAAM,iBAAiB,KAAK,SAAQ;GACpC,KAAK,OAAO,wBAAwB,GAAG,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAI,CAAE,CAAC;GAC9F,KAAK,WAAW,gBAAgB,GAAG,oBAAoB,UAAU,QAAQ,CAAC;GAI1E,KAAK,eAAe,IAAI,GAAG,YAAY;GACvC,IAAI,OAAO,mBAAmB,aAAa;GAC3C,MAAM,WAAW,IAAI,qBAAqB,KAAK,eAAe,IAAI,GAAG,YAAY,CAAC;GAClF,SAAS,QAAQ,EAAE;GACnB,KAAK,WAAW,gBAAgB,SAAS,WAAU,CAAE;EACvD,CAAC;CACH;CAEQ,KAAK,OAAkB,KAAW;EACxC,OAAO,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI;CAClD;CAGQ,OAAO,UAAmB;EAChC,IAAI,UAAU,KAAK,MAAM,GAAG;EAC5B,MAAM,MAAM,UAAU,KAAK,QAAQ;EACnC,KAAK,OAAO,QAAQ,QAAQ,KAAK,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,CAAC;CACnE;CAEU,WAAQ;EAChB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,KAAK,UAAU,IAAI,GAAG,SAAS;EAC/B,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EACtE,IAAI,KAAK,SAAQ,MAAO,YACtB,KAAK,OAAO,UAAS;GACnB,KAAK,SAAS,IAAI,UAAU;GAC5B,KAAK,kBAAkB,KAAK,EAAE,UAAU,WAAU,CAAE;EACtD,CAAC;CAEL;CAEU,iBAAc;EACtB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,GAAG,YAAY,GAAG;EAClB,KAAK,SAAS,IAAI,IAAI;CACxB;CAEU,cAAW;EACnB,KAAK,OAAO,QAAQ,MAAM,CAAC,CAAC;CAC9B;CAEU,cAAW;EACnB,KAAK,OAAO,IAAI,CAAA,CAAE;CACpB;CAEU,MAAM,YAAS;EACvB,MAAM,OAAO,KAAK,cAAa,CAAE,CAC9B,KAAK,MAAK;GAGT,OAAO,GAFI,EAAE,YAAY,IAAI,EAAE,UAAU,YAAW,EAAE,MAAO,KACjD,EAAE,QAAQ,IAAI,EAAE,MAAM,YAAW,EAAE,MAAO,KACjC,EAAE;EACzB,CAAC,CAAC,CACD,KAAK,IAAI;EACZ,IAAI;GACF,MAAM,UAAU,UAAU,UAAU,IAAI;EAC1C,QAAQ,CAGR;EACA,KAAK,QAAQ,KAAK,IAAI;CACxB;CAEU,eAAY;EACpB,KAAK,WAAW,QAAQ,MAAM,CAAC,CAAC;EAChC,IAAI,KAAK,WAAU,GAGjB,sBAAsB,KAAK,YAAW,CAAE,EAAE,cAAc,MAAK,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;OAE5F,KAAK,eAAe,IAAI,EAAE;CAE9B;CAEU,kBAAkB,OAAe;EACzC,KAAK,oBAAoB,QAAQ,QAAO;GACtC,MAAM,SAAS,IAAI,IAAI,GAAG;GAC1B,IAAI,OAAO,IAAI,KAAK,GAClB,OAAO,OAAO,KAAK;QAEnB,OAAO,IAAI,KAAK;GAElB,OAAO;EACT,CAAC;CACH;CAEU,YAAY,MAAa;EACjC,KAAK,UAAU,KAAK,IAAI;CAC1B;CAQU,cAAc,OAAsB,MAAa;EACzD,IAAI,CAAC,KAAK,eAAc,GAAI;EAC5B,IAAI,MAAM,QAAQ,SAAS;GACzB,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B,OAAO,IAAI,MAAM,QAAQ,KACvB,MAAM,eAAc;CAExB;CAEU,YAAY,OAAsB,MAAa;EACvD,IAAI,KAAK,eAAc,KAAM,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CAEU,UAAU,OAAoB;EACtC,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GAAI;GAC3C,MAAM,eAAc;GACpB,KAAK,aAAY;EACnB;EACA,IAAI,MAAM,QAAQ,OAAO,CAAC,MAAM,YAAY,CAAC,KAAK,WAAU,GAC1D,KAAK,gBAAe,CAAE,CAAC,cAAc,SAAS,GAAG,CAAC;EAEpD,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GACvC,KAAK,eAAc;CAEvB;CAEU,gBAAgB,MAAW;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,OAAO,KAAK,mBAAmB,KAAK,OAAM,GAAI,EAAE,QAAQ,MAAK,CAAgC;CAC/F;CASU,WAAW,MAAY;EAC/B,MAAM,QAAQ,KAAK,gBAAe;EAClC,MAAM,UAAU,QACZ,IAAI,OAAO,IAAI,MAAM,QAAQ,uBAAuB,MAAM,EAAC,IAAK,IAAI,IACpE;EAGJ,MAAM,cAAc,YAClB,UACI,QACG,MAAM,OAAO,CAAC,CACd,KAAK,MAAM,MACV,IAAI,MAAM,IACN,sCAAsC,KAAK,WAAW,IAAI,EAAC,WAC3D,KAAK,WAAW,IAAI,CAAC,CAC1B,CACA,KAAK,EAAE,IACV,KAAK,WAAW,OAAO;EAE7B,IAAI,CAAC,KAAK,WAAU,GAAI,OAAO,WAAW,IAAI;EAE9C,IAAI,OAAO;EACX,IAAI,YAAY;EAChB,IAAI,SAAS;EAGb,MAAM,KAAK;EACX,IAAI;EACJ,QAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;GACnC,QAAQ,WAAW,KAAK,MAAM,QAAQ,EAAE,KAAK,CAAC;GAC9C,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC;GACxB,KAAK,MAAM,SAAS,EAAE,MAAM,IAAA,CAAK,MAAM,GAAG,GACxC,IAAI,SAAS,OAAO,SAAS,IAAI;IAE/B,QAAQ,UAAU,OAAO,SAAS;IAClC,YAAY;GACd,OAAO,IAAI,aAAa,OAAO;IAC7B,QAAQ,gBAAgB,aAAa,MAAK;IAC1C;GACF;EAGJ;EACA,QAAQ,WAAW,KAAK,MAAM,MAAM,CAAC;EACrC,OAAO,OAAO,UAAU,OAAO,SAAS;CAC1C;CAEQ,WAAW,MAAY;EAC7B,OAAO,KACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM;CACzB;CAEU,cAAc,OAAY;EAClC,OAAQ,MAAM,OAA4B;CAC5C;CArcW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAlB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAkB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;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,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,kBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,WAAA;GAAA,SAAA;GAAA,mBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,iBAAA;GAAA,aAAA;GAAA,UAAA;EAAA,GAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,aAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UClG/B;EAsKA,QAAA,CAAA,wrUAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD7EY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eACR,CACb,iBAAiB;GAAC;GAAY;GAAa;GAAkB;GAAmB;GAAgB;EAAe,CAAC,CACjH;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAMU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA;EAZ9B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAe,YACb;GAAI,SACP,CAAC,aAAa;GAAC,eACT,CACb,iBAAiB;IAAC;IAAY;IAAa;IAAkB;IAAmB;IAAgB;GAAe,CAAC,CACjH;GAAA,iBAGgB,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,UAAA;GAAA,QAAA,CAAA,wrUAAA;EAAA,CAAA;CAU0C,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAiB,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACzB,eAAa,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,WAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,SAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-log-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n DestroyRef,\n ElementRef,\n Injector,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_ARROW_DOWN,\n ICON_CLIPBOARD,\n ICON_PLAYER_PAUSE,\n ICON_PLAYER_PLAY,\n ICON_SEARCH,\n ICON_TRASH,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\nexport type LogViewerVariant = 'terminal' | 'log';\n\nexport interface LogLine {\n text: string;\n level?: LogLevel;\n timestamp?: Date;\n source?: string;\n meta?: Record<string, unknown>;\n}\n\n/** SGR code → CSS class for the ANSI subset the viewer renders. */\nconst ANSI_CLASSES: Record<string, string> = {\n '1': 'ansi-bold',\n '3': 'ansi-italic',\n '30': 'ansi-black',\n '31': 'ansi-red',\n '32': 'ansi-green',\n '33': 'ansi-yellow',\n '34': 'ansi-blue',\n '35': 'ansi-magenta',\n '36': 'ansi-cyan',\n '37': 'ansi-white',\n '90': 'ansi-gray',\n '91': 'ansi-bright-red',\n '92': 'ansi-bright-green',\n '93': 'ansi-bright-yellow',\n '94': 'ansi-bright-blue',\n '95': 'ansi-bright-magenta',\n '96': 'ansi-bright-cyan',\n '97': 'ansi-bright-white',\n};\n\n/** Row height fallback when `--lc-log-viewer-line-height` can't be read (SSR, tests). */\nconst DEFAULT_LINE_HEIGHT = 22;\n\n/** Distance from the bottom (px) still counted as \"at the bottom\". */\nconst AT_BOTTOM_THRESHOLD = 30;\n\n/**\n * Streaming log / terminal viewer component.\n *\n * Supports controlled (lines input) and streaming (stream$ observable) modes,\n * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,\n * and filtering.\n *\n * The virtual window is sized from the *measured* height of the scroll area\n * (ResizeObserver), so any `height` — `400px`, `50vh`, `100%` inside a flex\n * parent — renders a full window. Row height comes from the CSS custom\n * property `--lc-log-viewer-line-height` (default 22px), read once after the\n * first render; override it on the host to change the row density.\n *\n * @example\n * ```html\n * <lc-log-viewer [stream$]=\"logs$\" autoScroll height=\"600px\" variant=\"terminal\" />\n * ```\n */\n@Component({\n selector: 'lc-log-viewer',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_TRASH, ICON_SEARCH, ICON_PLAYER_PLAY, ICON_PLAYER_PAUSE, ICON_CLIPBOARD, ICON_ARROW_DOWN]),\n ],\n templateUrl: './log-viewer.component.html',\n styleUrls: ['./log-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class LogViewerComponent {\n private static nextId = 0;\n /** Id of the search input (links its visually-hidden label). */\n protected readonly searchInputId = `lc-log-viewer-search-${++LogViewerComponent.nextId}`;\n\n private readonly ngZone = inject(NgZone);\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n private readonly scrollContainer = viewChild.required<ElementRef<HTMLElement>>('scrollContainer');\n private readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');\n private streamSub?: Subscription;\n\n /** Controlled mode: array of log lines */\n readonly lines = input<LogLine[]>([]);\n\n /** Streaming mode: observable of log lines */\n readonly stream$ = input<Observable<LogLine | LogLine[]>>();\n\n /** Maximum lines to keep in buffer */\n readonly maxLines = input<number>(10_000);\n\n /**\n * Follow new lines: scroll to the bottom whenever the visible line count\n * changes — but only while the reader is already at the bottom, so scrolling\n * up to inspect older output is never fought.\n */\n readonly autoScroll = input<boolean>(true);\n\n /** Show timestamps column */\n readonly showTimestamps = input<boolean>(true);\n\n /** Show line numbers */\n readonly showLineNumbers = input<boolean>(false);\n\n /** Parse ANSI color codes */\n readonly ansiColors = input<boolean>(true);\n\n /** Filter by log levels */\n readonly levelFilter = input<LogLevel[]>();\n\n /** Search query to highlight */\n readonly searchQuery = input<string>();\n\n /** Container height */\n readonly height = input<string>('400px');\n\n /** Visual variant */\n readonly variant = input<LogViewerVariant>('log');\n\n /**\n * Render lines as interactive rows (`role=\"button\"`, focusable, Enter/Space\n * emit `lineClick`). Set this whenever you bind `(lineClick)` so keyboard\n * users can activate a line too; a plain click emits either way.\n */\n readonly clickableLines = input<boolean>(false);\n\n /** BCP 47 locale of the line timestamps (24-hour clock). */\n readonly locale = input('en-US');\n\n /** Tooltip of the pause button while streaming. */\n readonly pauseLabel = input('Pause');\n\n /** Tooltip of the pause button while paused. */\n readonly resumeLabel = input('Resume');\n\n /** Accessible name of the pause button while streaming. */\n readonly pauseAriaLabel = input('Pause stream');\n\n /** Accessible name of the pause button while paused. */\n readonly resumeAriaLabel = input('Resume stream');\n\n /** Tooltip of the clear button. */\n readonly clearLabel = input('Clear');\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear log');\n\n /** Tooltip of the copy button. */\n readonly copyLabel = input('Copy all');\n\n /** Accessible name of the copy button. */\n readonly copyAriaLabel = input('Copy all lines');\n\n /** Tooltip of the search toggle. */\n readonly searchLabel = input('Search (/)');\n\n /** Accessible name of the search toggle. */\n readonly searchAriaLabel = input('Search log');\n\n /** Label of the search field (visually hidden). */\n readonly searchInputLabel = input('Search logs');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search logs...');\n\n /** Unit after the error count in the level filter. */\n readonly errorsLabel = input('errors');\n\n /** Unit after the warning count in the level filter. */\n readonly warningsLabel = input('warnings');\n\n /** Unit after the line count. */\n readonly linesLabel = input('lines');\n\n /** Accessible name of the log region. */\n readonly outputAriaLabel = input('Log output');\n\n /** Label of the jump-to-bottom button. */\n readonly jumpToBottomLabel = input('Jump to bottom');\n\n /** Emitted when a line is clicked (or activated with Enter/Space) */\n readonly lineClick = output<LogLine>();\n\n /**\n * Emitted with the copied text after \"Copy all\" — also when the Clipboard\n * API is unavailable (insecure context, denied permission), so consumers can\n * offer their own fallback.\n */\n readonly copyAll = output<string>();\n\n /** Emitted when scroll state changes */\n readonly scrollStateChange = output<{ atBottom: boolean }>();\n\n /** Internal buffer of all lines */\n protected buffer = signal<LogLine[]>([]);\n\n /** Whether stream is paused */\n protected paused = signal(false);\n\n /** Whether user is at bottom of scroll */\n protected atBottom = signal(true);\n\n /** Internal search input */\n protected internalSearch = signal('');\n\n /** Internal level filter */\n protected internalLevelFilter = signal<Set<LogLevel>>(new Set());\n\n /** Show search bar */\n protected showSearch = signal(false);\n\n /** Effective search query */\n protected effectiveSearch = computed(() => {\n return this.searchQuery() || this.internalSearch();\n });\n\n /** Filtered lines for display */\n protected filteredLines = computed(() => {\n let lines = this.buffer();\n const levels = this.levelFilter();\n const internalFilter = this.internalLevelFilter();\n\n // Apply level filter\n if (levels?.length) {\n lines = lines.filter((l) => l.level && levels.includes(l.level));\n } else if (internalFilter.size > 0) {\n lines = lines.filter((l) => l.level && internalFilter.has(l.level));\n }\n\n return lines;\n });\n\n // ── Virtual window ────────────────────────────────────────────────────\n\n protected scrollTop = signal(0);\n\n /**\n * Measured `clientHeight` of the scroll area (excludes toolbar and search\n * bar by construction). 0 until the first ResizeObserver callback.\n */\n private readonly viewportHeight = signal(0);\n\n /** Row height in px — `--lc-log-viewer-line-height`, read once after render. */\n protected readonly lineHeight = signal(DEFAULT_LINE_HEIGHT);\n\n protected visibleRange = computed(() => {\n const lineHeight = this.lineHeight();\n const measured = this.viewportHeight();\n // Before the first measurement fall back to the declared height (minus a\n // nominal toolbar) so the first frame already shows a plausible window.\n const viewHeight = measured > 0 ? measured : (parseInt(this.height(), 10) || 400) - 40;\n const total = this.filteredLines().length;\n const start = Math.floor(this.scrollTop() / lineHeight);\n const visible = Math.ceil(viewHeight / lineHeight) + 2;\n return {\n start: Math.max(0, start - 1),\n end: Math.min(total, start + visible + 1),\n total,\n totalHeight: total * lineHeight,\n };\n });\n\n protected visibleLines = computed(() => {\n const range = this.visibleRange();\n return this.filteredLines().slice(range.start, range.end).map((line, i) => ({\n ...line,\n _index: range.start + i,\n }));\n });\n\n protected containerClasses = computed(() => {\n return `lc-log-viewer lc-log-viewer--${this.variant()}`;\n });\n\n /** Line counts per level for toolbar */\n protected levelCounts = computed(() => {\n const buf = this.buffer();\n const counts = { debug: 0, info: 0, warn: 0, error: 0 };\n for (const line of buf) {\n if (line.level && line.level in counts) counts[line.level as keyof typeof counts]++;\n }\n return counts;\n });\n\n constructor() {\n // Controlled mode: mirror `lines` into the buffer — unconditionally, so\n // `[lines]=\"[]\"` clears the view. `maxLines` is read untracked: changing\n // it must not wipe a streamed buffer through this effect.\n effect(() => {\n const input = this.lines();\n untracked(() => this.buffer.set(this.trim(input, this.maxLines())));\n });\n\n // Streaming mode. The subscribe body runs untracked: BehaviorSubject /\n // ReplaySubject / of() emit synchronously inside this effect, and reading\n // `paused()` / `maxLines()` there would make them dependencies — toggling\n // Pause would then re-subscribe and replay duplicates.\n effect(() => {\n const stream = this.stream$();\n this.streamSub?.unsubscribe();\n this.streamSub = undefined;\n if (!stream) return;\n untracked(() => {\n this.streamSub = stream.subscribe((data) => this.append(Array.isArray(data) ? data : [data]));\n });\n });\n this.destroyRef.onDestroy(() => this.streamSub?.unsubscribe());\n\n // Follow mode: whenever the visible lines change (new lines, filter),\n // stick to the bottom — but only if the reader is already there.\n effect(() => {\n this.filteredLines();\n if (!this.autoScroll() || !untracked(this.atBottom)) return;\n afterNextRender(() => this.scrollToBottom(), { injector: this.injector });\n });\n\n afterNextRender(() => {\n const el = this.scrollContainer().nativeElement;\n\n // Row height from CSS (once) so the virtual window matches the rows.\n const declared = parseFloat(getComputedStyle(el).getPropertyValue('--lc-log-viewer-line-height'));\n if (declared > 0) this.lineHeight.set(declared);\n\n // Passive scroll tracking outside the zone.\n const onScroll = () => this.onScroll();\n this.ngZone.runOutsideAngular(() => el.addEventListener('scroll', onScroll, { passive: true }));\n this.destroyRef.onDestroy(() => el.removeEventListener('scroll', onScroll));\n\n // Measure the scroll area so `height=\"100%\"`, `50vh`, flex parents … all\n // yield a correctly sized window (not one derived from parseInt(height)).\n this.viewportHeight.set(el.clientHeight);\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => this.viewportHeight.set(el.clientHeight));\n observer.observe(el);\n this.destroyRef.onDestroy(() => observer.disconnect());\n });\n }\n\n private trim(lines: LogLine[], max: number): LogLine[] {\n return lines.length > max ? lines.slice(-max) : lines;\n }\n\n /** Append streamed lines to the buffer (no-op while paused). */\n private append(newLines: LogLine[]): void {\n if (untracked(this.paused)) return;\n const max = untracked(this.maxLines);\n this.buffer.update((buf) => this.trim([...buf, ...newLines], max));\n }\n\n protected onScroll(): void {\n const el = this.scrollContainer().nativeElement;\n this.scrollTop.set(el.scrollTop);\n const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < AT_BOTTOM_THRESHOLD;\n if (this.atBottom() !== isAtBottom) {\n this.ngZone.run(() => {\n this.atBottom.set(isAtBottom);\n this.scrollStateChange.emit({ atBottom: isAtBottom });\n });\n }\n }\n\n protected scrollToBottom(): void {\n const el = this.scrollContainer().nativeElement;\n el.scrollTop = el.scrollHeight;\n this.atBottom.set(true);\n }\n\n protected togglePause(): void {\n this.paused.update((v) => !v);\n }\n\n protected clearBuffer(): void {\n this.buffer.set([]);\n }\n\n protected async onCopyAll(): Promise<void> {\n const text = this.filteredLines()\n .map((l) => {\n const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';\n const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';\n return `${ts}${lvl}${l.text}`;\n })\n .join('\\n');\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n // Clipboard API unavailable (insecure context / permission denied):\n // still emit so the consumer can fall back to its own copy path.\n }\n this.copyAll.emit(text);\n }\n\n protected toggleSearch(): void {\n this.showSearch.update((v) => !v);\n if (this.showSearch()) {\n // The input is `@if`-rendered — focus it once it exists (`autofocus`\n // is unreliable for dynamically inserted elements).\n afterNextRender(() => this.searchInput()?.nativeElement.focus(), { injector: this.injector });\n } else {\n this.internalSearch.set('');\n }\n }\n\n protected toggleLevelFilter(level: LogLevel): void {\n this.internalLevelFilter.update((set) => {\n const newSet = new Set(set);\n if (newSet.has(level)) {\n newSet.delete(level);\n } else {\n newSet.add(level);\n }\n return newSet;\n });\n }\n\n protected onLineClick(line: LogLine): void {\n this.lineClick.emit(line);\n }\n\n /**\n * Enter/Space activate an interactive line (see `clickableLines`). Like a\n * native button, Enter fires on keydown and Space on keyup (keydown only\n * prevents the page scroll) — so a consumer that moves focus in its\n * `lineClick` handler doesn't get a stray Space keyup on the new target.\n */\n protected onLineKeydown(event: KeyboardEvent, line: LogLine): void {\n if (!this.clickableLines()) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this.lineClick.emit(line);\n } else if (event.key === ' ') {\n event.preventDefault();\n }\n }\n\n protected onLineKeyup(event: KeyboardEvent, line: LogLine): void {\n if (this.clickableLines() && event.key === ' ') {\n event.preventDefault();\n this.lineClick.emit(line);\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.showSearch()) {\n event.preventDefault();\n this.toggleSearch();\n }\n if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {\n this.scrollContainer().nativeElement.scrollTo(0, 0);\n }\n if (event.key === 'G' && !this.showSearch()) {\n this.scrollToBottom();\n }\n }\n\n protected formatTimestamp(date?: Date): string {\n if (!date) return '';\n return date.toLocaleTimeString(this.locale(), { hour12: false } as Intl.DateTimeFormatOptions);\n }\n\n /**\n * Render a line's text as HTML: ANSI SGR codes become `<span class=\"ansi-…\">`\n * wrappers, everything else is escaped, and search matches are wrapped in\n * `<mark>`. Highlighting is applied per *text* segment — before the spans\n * are assembled — so a query can never match inside a tag or an entity\n * (`e` used to hit `class=\"ansi-red\"`, `&` / `<` never matched at all).\n */\n protected renderLine(text: string): string {\n const query = this.effectiveSearch();\n const matcher = query\n ? new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi')\n : null;\n // Split the raw text on the query (capturing group → odd parts are the\n // matches), escape every part, wrap the matches.\n const renderText = (segment: string): string =>\n matcher\n ? segment\n .split(matcher)\n .map((part, i) =>\n i % 2 === 1\n ? `<mark class=\"lc-log-viewer__match\">${this.escapeHtml(part)}</mark>`\n : this.escapeHtml(part),\n )\n .join('')\n : this.escapeHtml(segment);\n\n if (!this.ansiColors()) return renderText(text);\n\n let html = '';\n let openSpans = 0;\n let cursor = 0;\n // SGR sequences: ESC [ <params> m — params are `;`-separated (may be empty = reset).\n // eslint-disable-next-line no-control-regex -- matching the ESC byte is the point here\n const re = /\\x1b\\[([\\d;]*)m/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n html += renderText(text.slice(cursor, m.index));\n cursor = m.index + m[0].length;\n for (const code of (m[1] || '0').split(';')) {\n if (code === '0' || code === '') {\n // Reset closes everything that is open.\n html += '</span>'.repeat(openSpans);\n openSpans = 0;\n } else if (ANSI_CLASSES[code]) {\n html += `<span class=\"${ANSI_CLASSES[code]}\">`;\n openSpans++;\n }\n // Unrecognised codes are stripped.\n }\n }\n html += renderText(text.slice(cursor));\n return html + '</span>'.repeat(openSpans);\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLInputElement).value;\n }\n}\n","<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n"],"mappings":";;;;AAyCA,MAAM,eAAuC;CAC3C,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;AACP;AAGD,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAgC5B,IAAa,qBAAb,MAAa,mBAAkB;CACrB,OAAO,SAAS;CAEL,gBAAgB,wBAAwB,EAAE,mBAAmB;CAE/D,SAAS,OAAO,MAAM;CACtB,WAAW,OAAO,QAAQ;CAC1B,aAAa,OAAO,UAAU;CAC9B,kBAAkB,UAAU,SAAkC,mBAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAChF,cAAc,UAAwC,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC7E;CAGC,QAAQ,MAAiB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAG5B,UAAU,MAAwC,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGlD,WAAW,MAAc,KAAO,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAOhC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,iBAAiB,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,kBAAkB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAmB,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,SAAS,MAAc,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,UAAU,MAAwB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAOxC,iBAAiB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,SAAS,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,iBAAiB,MAAM,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,kBAAkB,MAAM,iBAAgB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,iBAAiB,MAAM,aAAY,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGnC,YAAY,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,cAAc,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,mBAAmB,MAAM,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,YAAY,OAAM;CAOlB,UAAU,OAAM;CAGhB,oBAAoB,OAAM;CAGzB,SAAS,OAAkB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,SAAS,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGtB,WAAW,OAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,iBAAiB,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,sBAAsB,uBAAsB,IAAI,IAAG,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtD,aAAa,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG1B,kBAAkB,eAAc;EACxC,OAAO,KAAK,YAAW,KAAM,KAAK,eAAc;CAClD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,gBAAgB,eAAc;EACtC,IAAI,QAAQ,KAAK,OAAM;EACvB,MAAM,SAAS,KAAK,YAAW;EAC/B,MAAM,iBAAiB,KAAK,oBAAmB;EAG/C,IAAI,QAAQ,QACV,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,OAAO,SAAS,EAAE,KAAK,CAAC;OAC1D,IAAI,eAAe,OAAO,GAC/B,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,eAAe,IAAI,EAAE,KAAK,CAAC;EAGpE,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAIQ,YAAY,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAMd,iBAAiB,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,aAAa,OAAO,qBAAoB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEjD,eAAe,eAAc;EACrC,MAAM,aAAa,KAAK,WAAU;EAClC,MAAM,WAAW,KAAK,eAAc;EAGpC,MAAM,aAAa,WAAW,IAAI,YAAY,SAAS,KAAK,OAAM,GAAI,EAAE,KAAK,OAAO;EACpF,MAAM,QAAQ,KAAK,cAAa,CAAE,CAAC;EACnC,MAAM,QAAQ,KAAK,MAAM,KAAK,UAAS,IAAK,UAAU;EACtD,MAAM,UAAU,KAAK,KAAK,aAAa,UAAU,IAAI;EACrD,OAAO;GACL,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC;GAC5B,KAAK,KAAK,IAAI,OAAO,QAAQ,UAAU,CAAC;GACxC;GACA,aAAa,QAAQ;EACtB;CACH,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,eAAe,eAAc;EACrC,MAAM,QAAQ,KAAK,aAAY;EAC/B,OAAO,KAAK,cAAa,CAAE,CAAC,MAAM,MAAM,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO;GAC1E,GAAG;GACH,QAAQ,MAAM,QAAQ;EACvB,EAAC;CACJ,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,mBAAmB,eAAc;EACzC,OAAO,gCAAgC,KAAK,QAAO;CACrD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,cAAc,eAAc;EACpC,MAAM,MAAM,KAAK,OAAM;EACvB,MAAM,SAAS;GAAE,OAAO;GAAG,MAAM;GAAG,MAAM;GAAG,OAAO;EAAC;EACrD,KAAK,MAAM,QAAQ,KACjB,IAAI,KAAK,SAAS,KAAK,SAAS,QAAQ,OAAO,KAAK,MAA6B;EAEnF,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EAIE,aAAY;GACV,MAAM,QAAQ,KAAK,MAAK;GACxB,gBAAgB,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,SAAQ,CAAE,CAAC,CAAC;EACpE,CAAC;EAMD,aAAY;GACV,MAAM,SAAS,KAAK,QAAO;GAC3B,KAAK,WAAW,YAAW;GAC3B,KAAK,YAAY;GACjB,IAAI,CAAC,QAAQ;GACb,gBAAe;IACb,KAAK,YAAY,OAAO,WAAW,SAAS,KAAK,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;GAC9F,CAAC;EACH,CAAC;EACD,KAAK,WAAW,gBAAgB,KAAK,WAAW,YAAW,CAAE;EAI7D,aAAY;GACV,KAAK,cAAa;GAClB,IAAI,CAAC,KAAK,WAAU,KAAM,CAAC,UAAU,KAAK,QAAQ,GAAG;GACrD,sBAAsB,KAAK,eAAc,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;EAC1E,CAAC;EAED,sBAAqB;GACnB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;GAGlC,MAAM,WAAW,WAAW,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,6BAA6B,CAAC;GAChG,IAAI,WAAW,GAAG,KAAK,WAAW,IAAI,QAAQ;GAG9C,MAAM,iBAAiB,KAAK,SAAQ;GACpC,KAAK,OAAO,wBAAwB,GAAG,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAI,CAAE,CAAC;GAC9F,KAAK,WAAW,gBAAgB,GAAG,oBAAoB,UAAU,QAAQ,CAAC;GAI1E,KAAK,eAAe,IAAI,GAAG,YAAY;GACvC,IAAI,OAAO,mBAAmB,aAAa;GAC3C,MAAM,WAAW,IAAI,qBAAqB,KAAK,eAAe,IAAI,GAAG,YAAY,CAAC;GAClF,SAAS,QAAQ,EAAE;GACnB,KAAK,WAAW,gBAAgB,SAAS,WAAU,CAAE;EACvD,CAAC;CACH;CAEQ,KAAK,OAAkB,KAAW;EACxC,OAAO,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI;CAClD;CAGQ,OAAO,UAAmB;EAChC,IAAI,UAAU,KAAK,MAAM,GAAG;EAC5B,MAAM,MAAM,UAAU,KAAK,QAAQ;EACnC,KAAK,OAAO,QAAQ,QAAQ,KAAK,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,CAAC;CACnE;CAEU,WAAQ;EAChB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,KAAK,UAAU,IAAI,GAAG,SAAS;EAC/B,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EACtE,IAAI,KAAK,SAAQ,MAAO,YACtB,KAAK,OAAO,UAAS;GACnB,KAAK,SAAS,IAAI,UAAU;GAC5B,KAAK,kBAAkB,KAAK,EAAE,UAAU,WAAU,CAAE;EACtD,CAAC;CAEL;CAEU,iBAAc;EACtB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,GAAG,YAAY,GAAG;EAClB,KAAK,SAAS,IAAI,IAAI;CACxB;CAEU,cAAW;EACnB,KAAK,OAAO,QAAQ,MAAM,CAAC,CAAC;CAC9B;CAEU,cAAW;EACnB,KAAK,OAAO,IAAI,CAAA,CAAE;CACpB;CAEU,MAAM,YAAS;EACvB,MAAM,OAAO,KAAK,cAAa,CAAE,CAC9B,KAAK,MAAK;GAGT,OAAO,GAFI,EAAE,YAAY,IAAI,EAAE,UAAU,YAAW,EAAE,MAAO,KACjD,EAAE,QAAQ,IAAI,EAAE,MAAM,YAAW,EAAE,MAAO,KACjC,EAAE;EACzB,CAAC,CAAC,CACD,KAAK,IAAI;EACZ,IAAI;GACF,MAAM,UAAU,UAAU,UAAU,IAAI;EAC1C,QAAQ,CAGR;EACA,KAAK,QAAQ,KAAK,IAAI;CACxB;CAEU,eAAY;EACpB,KAAK,WAAW,QAAQ,MAAM,CAAC,CAAC;EAChC,IAAI,KAAK,WAAU,GAGjB,sBAAsB,KAAK,YAAW,CAAE,EAAE,cAAc,MAAK,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;OAE5F,KAAK,eAAe,IAAI,EAAE;CAE9B;CAEU,kBAAkB,OAAe;EACzC,KAAK,oBAAoB,QAAQ,QAAO;GACtC,MAAM,SAAS,IAAI,IAAI,GAAG;GAC1B,IAAI,OAAO,IAAI,KAAK,GAClB,OAAO,OAAO,KAAK;QAEnB,OAAO,IAAI,KAAK;GAElB,OAAO;EACT,CAAC;CACH;CAEU,YAAY,MAAa;EACjC,KAAK,UAAU,KAAK,IAAI;CAC1B;CAQU,cAAc,OAAsB,MAAa;EACzD,IAAI,CAAC,KAAK,eAAc,GAAI;EAC5B,IAAI,MAAM,QAAQ,SAAS;GACzB,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B,OAAO,IAAI,MAAM,QAAQ,KACvB,MAAM,eAAc;CAExB;CAEU,YAAY,OAAsB,MAAa;EACvD,IAAI,KAAK,eAAc,KAAM,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CAEU,UAAU,OAAoB;EACtC,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GAAI;GAC3C,MAAM,eAAc;GACpB,KAAK,aAAY;EACnB;EACA,IAAI,MAAM,QAAQ,OAAO,CAAC,MAAM,YAAY,CAAC,KAAK,WAAU,GAC1D,KAAK,gBAAe,CAAE,CAAC,cAAc,SAAS,GAAG,CAAC;EAEpD,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GACvC,KAAK,eAAc;CAEvB;CAEU,gBAAgB,MAAW;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,OAAO,KAAK,mBAAmB,KAAK,OAAM,GAAI,EAAE,QAAQ,MAAK,CAAgC;CAC/F;CASU,WAAW,MAAY;EAC/B,MAAM,QAAQ,KAAK,gBAAe;EAClC,MAAM,UAAU,QACZ,IAAI,OAAO,IAAI,MAAM,QAAQ,uBAAuB,MAAM,EAAC,IAAK,IAAI,IACpE;EAGJ,MAAM,cAAc,YAClB,UACI,QACG,MAAM,OAAO,CAAC,CACd,KAAK,MAAM,MACV,IAAI,MAAM,IACN,sCAAsC,KAAK,WAAW,IAAI,EAAC,WAC3D,KAAK,WAAW,IAAI,CAAC,CAC1B,CACA,KAAK,EAAE,IACV,KAAK,WAAW,OAAO;EAE7B,IAAI,CAAC,KAAK,WAAU,GAAI,OAAO,WAAW,IAAI;EAE9C,IAAI,OAAO;EACX,IAAI,YAAY;EAChB,IAAI,SAAS;EAGb,MAAM,KAAK;EACX,IAAI;EACJ,QAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;GACnC,QAAQ,WAAW,KAAK,MAAM,QAAQ,EAAE,KAAK,CAAC;GAC9C,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC;GACxB,KAAK,MAAM,SAAS,EAAE,MAAM,IAAA,CAAK,MAAM,GAAG,GACxC,IAAI,SAAS,OAAO,SAAS,IAAI;IAE/B,QAAQ,UAAU,OAAO,SAAS;IAClC,YAAY;GACd,OAAO,IAAI,aAAa,OAAO;IAC7B,QAAQ,gBAAgB,aAAa,MAAK;IAC1C;GACF;EAGJ;EACA,QAAQ,WAAW,KAAK,MAAM,MAAM,CAAC;EACrC,OAAO,OAAO,UAAU,OAAO,SAAS;CAC1C;CAEQ,WAAW,MAAY;EAC7B,OAAO,KACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM;CACzB;CAEU,cAAc,OAAY;EAClC,OAAQ,MAAM,OAA4B;CAC5C;CArcW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAlB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAkB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;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,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,kBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,WAAA;GAAA,SAAA;GAAA,mBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,iBAAA;GAAA,aAAA;GAAA,UAAA;EAAA,GAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,aAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UClG/B;EAsKA,QAAA,CAAA,w0UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD7EY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eACR,CACb,iBAAiB;GAAC;GAAY;GAAa;GAAkB;GAAmB;GAAgB;EAAe,CAAC,CACjH;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAMU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA;EAZ9B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAe,YACb;GAAI,SACP,CAAC,aAAa;GAAC,eACT,CACb,iBAAiB;IAAC;IAAY;IAAa;IAAkB;IAAmB;IAAgB;GAAe,CAAC,CACjH;GAAA,iBAGgB,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,UAAA;GAAA,QAAA,CAAA,w0UAAA;EAAA,CAAA;CAU0C,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAiB,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACzB,eAAa,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,WAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,SAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-next-step-card.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/next-step-card/next-step-card.component.ts","../../../../libs/angular-ui-kit/src/lib/next-step-card/next-step-card.component.html"],"sourcesContent":["import {\n AfterViewChecked,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n effect,\n input,\n isDevMode,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_RIGHT, ICON_EXTERNAL_LINK, ICON_INFORMATION_CIRCLE } from '@lifecorner/angular-ui-kit/icons';\n\nexport type NextStepCardTone = 'neutral' | 'primary' | 'success' | 'warning' | 'info';\n\n/**\n * Appearance of the card.\n * - `default`: white surface, hairline border, 4 px tonal rail, 36 px tinted bubble\n * - `soft`: tinted fill per tone, radius 24 px (28 px from 1024 px), no rail,\n * border or shadow, a larger white bubble with a 1 px ring (the \"Weite\" look)\n */\nexport type NextStepCardVariant = 'default' | 'soft';\n\n/**\n * How several actions are laid out.\n * - `inline`: side by side at the end of the card; below 480 px card width in\n * their own row, stretched\n * - `stacked`: always in their own row, one per line at equal width, 8 px apart\n */\nexport type NextStepCardActionsLayout = 'inline' | 'stacked';\n\n/**\n * Weight of the two built-in actions.\n * - `default`: a filled primary and a ghost secondary with a chevron, the\n * primary last\n * - `equal`: two answers of the same weight (a question such as „Teilen?“):\n * both `outline` in the primary tone at `actionSize`, the primary first,\n * no chevron\n */\nexport type NextStepCardActionsStyle = 'default' | 'equal';\n\n/** Element that renders the title (semantics only, the look stays the same). */\nexport type NextStepCardTitleTag = 'div' | 'h2' | 'h3';\n\n/** Size of the built-in action buttons (`sm` 32 px, `md` 42 px, `lg` 48.5 px). */\nexport type NextStepCardActionSize = 'sm' | 'md' | 'lg';\n\n/**\n * Next-step card — the single status-and-action surface of a page: \"where does\n * this stand, and what is the one next step?\".\n *\n * Features:\n * - Tonal accent rail (4px left) + tinted icon bubble, token colors only\n * - Title, message (string input or default slot for markup)\n * - Primary action (with loading / disabled + mandatory disabled reason),\n * ghost secondary action, or an external link instead of the primary button\n * - `[slot='blockers']`: rows inside the card under a subtle divider (dot\n * prefix); each projected element with `slot=\"blockers\"` becomes one row —\n * blockers are rows IN the card, never additional banners\n * - `[slot='meta']`: one footer line (icon + text)\n * - `[slot='actions']`: fully replaces the built-in action buttons\n * - `[slot='body']`: content below the message without the message's text\n * styles, so form fields, lists or a description list keep their own look\n * - `role=\"region\"` labelled by the title; optional polite live announcement\n * of title changes via `announce`\n * - Narrow layout: below 480 px of card width (a CSS container query on the\n * host) the icon sits beside the title, message, body, blockers and meta\n * take the full inner width, the inline padding drops to `padding-md`, and\n * the actions take their own row with every action stretched. A projected\n * `fullWidth` button gets its own row at every width.\n * - `variant=\"soft\"`: the tinted card of the \"Weite\" look; `actionsLayout`\n * `stacked` for several equal-width actions; `titleTag` for a heading\n * - `actionsStyle=\"equal\"` (3.16, BA-5): a question with two answers of the\n * same weight, both outline buttons in the primary tone, primary first\n *\n * The host is a size container (`container-type: inline-size`) and takes the\n * full inline size of its parent.\n *\n * Usage rule: exactly ONE next-step card per page.\n *\n * @example\n * ```html\n * <lc-next-step-card\n * tone=\"warning\"\n * icon=\"clock\"\n * cardTitle=\"Wartet auf Freigabe\"\n * message=\"Der Entwurf ist vollständig und kann geprüft werden.\"\n * primaryLabel=\"Freigeben\"\n * secondaryLabel=\"Änderungen anfordern\"\n * (primaryClick)=\"approve()\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-next-step-card',\n standalone: true,\n imports: [ButtonComponent, IconComponent],\n // Its default `icon` and the action glyphs.\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHEVRON_RIGHT, ICON_EXTERNAL_LINK])],\n templateUrl: './next-step-card.component.html',\n styleUrls: ['./next-step-card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NextStepCardComponent implements AfterViewChecked {\n /**\n * Tone of the accent rail and icon bubble. Colors come exclusively from the\n * semantic tokens (`--color-*` rail, `--color-*-subtle` bubble tint,\n * `--color-on-*-subtle` icon ink).\n * @default 'neutral'\n */\n tone = input<NextStepCardTone>('neutral');\n\n /**\n * Appearance: `default` (surface, border, 4 px rail) or `soft` (tinted fill\n * per tone, radius 24 px / 28 px from 1024 px, no rail, border or shadow,\n * a 48 px white bubble with a 1 px ring; 56 px from 1024 px, 60 px from\n * 1440 px). Soft fills come from `--color-next-step-soft-neutral` /\n * `--color-next-step-soft-primary` and the tone's `-subtle` token; the bubble\n * from `--color-bubble`, `--color-bubble-ring`, `--color-bubble-icon`.\n * @default 'default'\n */\n variant = input<NextStepCardVariant>('default');\n\n /** Icon name for the round bubble (36 px; 48–60 px in the soft variant). */\n icon = input<string>('information-circle');\n\n /** Title (1rem semibold; 20–28 px bold in the soft variant). Also the card's accessible name. */\n cardTitle = input.required<string>();\n\n /**\n * Element that renders the title: `div` (default), `h2` or `h3`. Use a\n * heading when the page has sections, so heading navigation reaches the\n * most important item. Semantics only, the look stays the same.\n * @default 'div'\n */\n titleTag = input<NextStepCardTitleTag>('div');\n\n /**\n * Layout of several actions. `inline`: side by side at the end of the card\n * (below 480 px card width in their own row, stretched). `stacked`: always\n * in their own row, one per line at equal width, 8 px apart (e.g. a list of\n * sign-in methods).\n * @default 'inline'\n */\n actionsLayout = input<NextStepCardActionsLayout>('inline');\n\n /**\n * Size of the built-in action buttons and link: `sm` (32 px, the 3.2\n * default), `md` (42 px) or `lg` (48.5 px, meets the 44 px touch target).\n * @default 'sm'\n */\n actionSize = input<NextStepCardActionSize>('sm');\n\n /**\n * Weight of the built-in actions. `default`: filled primary, ghost secondary\n * with a chevron, primary last. `equal`: two answers of the same weight, both\n * `outline` in the primary tone at `actionSize`, primary first, no chevron\n * (`primaryClick`, `secondaryClick`, `primaryLoading`, `primaryDisabled` and\n * `announce` work as before). Use `actionSize=\"lg\"` (48.5 px, the 44 px\n * target; a dev warning otherwise). No effect on `[slot='actions']`.\n * @default 'default'\n */\n actionsStyle = input<NextStepCardActionsStyle>('default');\n\n /** Message below the title. For markup, use the default slot instead. */\n message = input<string>();\n\n /** Label of the primary action. Omit to render no primary button. */\n primaryLabel = input<string>();\n\n /** Optional leading icon of the primary action. */\n primaryIcon = input<string>();\n\n /** Shows a spinner on the primary action. */\n primaryLoading = input<boolean>(false);\n\n /**\n * Disables the primary action. A disabled primary REQUIRES\n * `primaryDisabledReason`; disabled without a reason logs a dev warning.\n */\n primaryDisabled = input<boolean>(false);\n\n /** Why the primary action is disabled — shown as tooltip on the button. */\n primaryDisabledReason = input<string>('');\n\n /** Label of the ghost secondary action (rendered with a chevron). */\n secondaryLabel = input<string>();\n\n /**\n * Label of an external link rendered instead of the primary button\n * (opens in a new tab). Requires `linkHref`.\n */\n linkLabel = input<string>();\n\n /** Target URL of the external link. */\n linkHref = input<string>();\n\n /**\n * Where the link opens: `_blank` (a new tab, with `rel=\"noopener noreferrer\"`,\n * the 3.2 behaviour) or `_self` (the same tab, no `rel`).\n * @default '_blank'\n */\n linkTarget = input<'_blank' | '_self'>('_blank');\n\n /**\n * Announce title changes to screen readers via `aria-live=\"polite\"` —\n * for cards whose state advances in place.\n * @default false\n */\n announce = input<boolean>(false);\n\n /** Emits when the primary action is clicked. */\n primaryClick = output<void>();\n\n /** Emits when the secondary action is clicked. */\n secondaryClick = output<void>();\n\n private actionsSlot = viewChild<ElementRef<HTMLElement>>('actionsSlot');\n\n /**\n * Whether content was projected into `[slot='actions']` — projected actions\n * fully replace the built-in buttons.\n */\n protected hasProjectedActions = signal(false);\n\n protected cardClasses = computed(() =>\n [\n 'lc-next-step-card',\n `lc-next-step-card--${this.tone()}`,\n this.variant() === 'soft' ? 'lc-next-step-card--soft' : null,\n this.actionsLayout() === 'stacked' ? 'lc-next-step-card--actions-stacked' : null,\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n /** `rel` of the external link: only a new tab needs `noopener noreferrer`. */\n protected linkRel = computed(() => (this.linkTarget() === '_blank' ? 'noopener noreferrer' : null));\n\n protected showLink = computed(() => !!(this.linkLabel() && this.linkHref()));\n\n /**\n * Whether the card renders any action: hides the empty actions box, so the\n * narrow layout gets no empty row.\n */\n protected hasActions = computed(\n () =>\n this.hasProjectedActions() ||\n !!this.secondaryLabel() ||\n this.showLink() ||\n !!this.primaryLabel(),\n );\n\n constructor() {\n effect(() => {\n if (isDevMode() && this.primaryDisabled() && !this.primaryDisabledReason()) {\n console.warn(\n '[lc-next-step-card] `primaryDisabled` is set without a `primaryDisabledReason`. ' +\n 'A disabled primary action must explain why (shown as tooltip).',\n );\n }\n });\n effect(() => {\n if (isDevMode() && this.actionsStyle() === 'equal' && this.actionSize() !== 'lg') {\n console.warn('[lc-next-step-card] actionsStyle=\"equal\" needs actionSize=\"lg\" (44 px)');\n }\n });\n }\n\n ngAfterViewChecked(): void {\n // Projected [slot='actions'] content replaces the built-in buttons. The\n // signal write is equality-guarded, so unchanged checks are no-ops.\n const el = this.actionsSlot()?.nativeElement;\n this.hasProjectedActions.set(!!el && el.childElementCount > 0);\n }\n}\n","<div [class]=\"cardClasses()\" role=\"region\" [attr.aria-label]=\"cardTitle()\">\n <div class=\"lc-next-step-card__icon\" aria-hidden=\"true\">\n <lc-icon [name]=\"icon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n\n <div class=\"lc-next-step-card__body\">\n @switch (titleTag()) {\n @case ('h2') {\n <h2 class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </h2>\n }\n @case ('h3') {\n <h3 class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </h3>\n }\n @default {\n <div class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </div>\n }\n }\n @if (message()) {\n <p class=\"lc-next-step-card__message\">{{ message() }}</p>\n }\n <div class=\"lc-next-step-card__custom-message\">\n <ng-content />\n </div>\n <!-- Form-safe body: no inherited message text styles. -->\n <div class=\"lc-next-step-card__content\">\n <ng-content select=\"[slot='body']\" />\n </div>\n <div class=\"lc-next-step-card__blockers\">\n <ng-content select=\"[slot='blockers']\" />\n </div>\n <div class=\"lc-next-step-card__meta\">\n <ng-content select=\"[slot='meta']\" />\n </div>\n </div>\n\n <div class=\"lc-next-step-card__actions\" [class.lc-next-step-card__actions--empty]=\"!hasActions()\">\n <div #actionsSlot class=\"lc-next-step-card__actions-slot\">\n <ng-content select=\"[slot='actions']\" />\n </div>\n @if (!hasProjectedActions() && actionsStyle() === 'equal') {\n <!-- Two answers of the same weight (3.16, BA-5): the primary first, both outline in the primary tone. -->\n @if (showLink()) {\n <a\n class=\"btn btn-secondary btn-{{ actionSize() }} lc-next-step-card__link\"\n [href]=\"linkHref()\"\n [attr.target]=\"linkTarget()\"\n [attr.rel]=\"linkRel()\"\n >\n <lc-icon name=\"external-link\" size=\"sm\" [decorative]=\"true\" />\n {{ linkLabel() }}\n </a>\n } @else if (primaryLabel()) {\n <lc-button\n variant=\"outline\"\n tone=\"primary\"\n [size]=\"actionSize()\"\n [loading]=\"primaryLoading()\"\n [disabled]=\"primaryDisabled()\"\n [disabledReason]=\"primaryDisabledReason()\"\n (clicked)=\"primaryClick.emit()\"\n >\n @if (primaryIcon()) {\n <lc-icon slot=\"icon-before\" [name]=\"primaryIcon()!\" size=\"sm\" [decorative]=\"true\" />\n }\n {{ primaryLabel() }}\n </lc-button>\n }\n @if (secondaryLabel()) {\n <lc-button variant=\"outline\" tone=\"primary\" [size]=\"actionSize()\" (clicked)=\"secondaryClick.emit()\">\n {{ secondaryLabel() }}\n </lc-button>\n }\n } @else if (!hasProjectedActions()) {\n @if (secondaryLabel()) {\n <lc-button variant=\"ghost\" [size]=\"actionSize()\" (clicked)=\"secondaryClick.emit()\">\n {{ secondaryLabel() }}\n <lc-icon slot=\"icon-after\" name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </lc-button>\n }\n @if (showLink()) {\n <a\n class=\"btn btn-secondary btn-{{ actionSize() }} lc-next-step-card__link\"\n [href]=\"linkHref()\"\n [attr.target]=\"linkTarget()\"\n [attr.rel]=\"linkRel()\"\n >\n <lc-icon name=\"external-link\" size=\"sm\" [decorative]=\"true\" />\n {{ linkLabel() }}\n </a>\n } @else if (primaryLabel()) {\n <lc-button\n variant=\"primary\"\n [size]=\"actionSize()\"\n [loading]=\"primaryLoading()\"\n [disabled]=\"primaryDisabled()\"\n [disabledReason]=\"primaryDisabledReason()\"\n (clicked)=\"primaryClick.emit()\"\n >\n @if (primaryIcon()) {\n <lc-icon slot=\"icon-before\" [name]=\"primaryIcon()!\" size=\"sm\" [decorative]=\"true\" />\n }\n {{ primaryLabel() }}\n </lc-button>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-next-step-card.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/next-step-card/next-step-card.component.ts","../../../../libs/angular-ui-kit/src/lib/next-step-card/next-step-card.component.html"],"sourcesContent":["import {\n AfterViewChecked,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n effect,\n input,\n isDevMode,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { ButtonComponent } from '@lifecorner/angular-ui-kit/button';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_RIGHT, ICON_EXTERNAL_LINK, ICON_INFORMATION_CIRCLE } from '@lifecorner/angular-ui-kit/icons';\n\nexport type NextStepCardTone = 'neutral' | 'primary' | 'success' | 'warning' | 'info';\n\n/**\n * Appearance of the card.\n * - `default`: white surface, hairline border, 4 px tonal rail, 36 px tinted bubble\n * - `soft`: tinted fill per tone, radius 24 px (28 px from 1024 px), no rail,\n * border or shadow, a larger white bubble with a 1 px ring (the \"Weite\" look)\n */\nexport type NextStepCardVariant = 'default' | 'soft';\n\n/**\n * How several actions are laid out.\n * - `inline`: side by side at the end of the card; below 480 px card width in\n * their own row, stretched\n * - `stacked`: always in their own row, one per line at equal width, 8 px apart\n */\nexport type NextStepCardActionsLayout = 'inline' | 'stacked';\n\n/**\n * Weight of the two built-in actions.\n * - `default`: a filled primary and a ghost secondary with a chevron, the\n * primary last\n * - `equal`: two answers of the same weight (a question such as „Teilen?“):\n * both `outline` in the primary tone at `actionSize`, the primary first,\n * no chevron\n */\nexport type NextStepCardActionsStyle = 'default' | 'equal';\n\n/** Element that renders the title (semantics only, the look stays the same). */\nexport type NextStepCardTitleTag = 'div' | 'h2' | 'h3';\n\n/** Size of the built-in action buttons (`sm` 32 px, `md` 42 px, `lg` 48.5 px). */\nexport type NextStepCardActionSize = 'sm' | 'md' | 'lg';\n\n/**\n * Next-step card — the single status-and-action surface of a page: \"where does\n * this stand, and what is the one next step?\".\n *\n * Features:\n * - Tonal accent rail (4px left) + tinted icon bubble, token colors only\n * - Title, message (string input or default slot for markup)\n * - Primary action (with loading / disabled + mandatory disabled reason),\n * ghost secondary action, or an external link instead of the primary button\n * - `[slot='blockers']`: rows inside the card under a subtle divider (dot\n * prefix); each projected element with `slot=\"blockers\"` becomes one row —\n * blockers are rows IN the card, never additional banners\n * - `[slot='meta']`: one footer line (icon + text)\n * - `[slot='actions']`: fully replaces the built-in action buttons\n * - `[slot='body']`: content below the message without the message's text\n * styles, so form fields, lists or a description list keep their own look\n * - `role=\"region\"` labelled by the title; optional polite live announcement\n * of title changes via `announce`\n * - Narrow layout: below 480 px of card width (a CSS container query on the\n * host) the icon sits beside the title, message, body, blockers and meta\n * take the full inner width, the inline padding drops to `padding-md`, and\n * the actions take their own row with every action stretched. A projected\n * `fullWidth` button gets its own row at every width.\n * - `variant=\"soft\"`: the tinted card of the \"Weite\" look; `actionsLayout`\n * `stacked` for several equal-width actions; `titleTag` for a heading\n * - `actionsStyle=\"equal\"` (3.16, BA-5): a question with two answers of the\n * same weight, both outline buttons in the primary tone, primary first\n *\n * The host is a size container (`container-type: inline-size`) and takes the\n * full inline size of its parent.\n *\n * Usage rule: exactly ONE next-step card per page.\n *\n * @example\n * ```html\n * <lc-next-step-card\n * tone=\"warning\"\n * icon=\"clock\"\n * cardTitle=\"Wartet auf Freigabe\"\n * message=\"Der Entwurf ist vollständig und kann geprüft werden.\"\n * primaryLabel=\"Freigeben\"\n * secondaryLabel=\"Änderungen anfordern\"\n * (primaryClick)=\"approve()\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-next-step-card',\n standalone: true,\n imports: [ButtonComponent, IconComponent],\n // Its default `icon` and the action glyphs.\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHEVRON_RIGHT, ICON_EXTERNAL_LINK])],\n templateUrl: './next-step-card.component.html',\n styleUrls: ['./next-step-card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NextStepCardComponent implements AfterViewChecked {\n /**\n * Tone of the accent rail and icon bubble. Colors come exclusively from the\n * semantic tokens (`--color-*` rail, `--color-*-subtle` bubble tint,\n * `--color-on-*-subtle` icon ink).\n * @default 'neutral'\n */\n tone = input<NextStepCardTone>('neutral');\n\n /**\n * Appearance: `default` (surface, border, 4 px rail) or `soft` (tinted fill\n * per tone, radius 24 px / 28 px from 1024 px, no rail, border or shadow,\n * a 48 px white bubble with a 1 px ring; 56 px from 1024 px, 60 px from\n * 1440 px). Soft fills come from `--color-next-step-soft-neutral` /\n * `--color-next-step-soft-primary` and the tone's `-subtle` token; the bubble\n * from `--color-bubble`, `--color-bubble-ring`, `--color-bubble-icon`.\n * @default 'default'\n */\n variant = input<NextStepCardVariant>('default');\n\n /** Icon name for the round bubble (36 px; 48–60 px in the soft variant). */\n icon = input<string>('information-circle');\n\n /** Title (1rem semibold; 20–28 px bold in the soft variant). Also the card's accessible name. */\n cardTitle = input.required<string>();\n\n /**\n * Element that renders the title: `div` (default), `h2` or `h3`. Use a\n * heading when the page has sections, so heading navigation reaches the\n * most important item. Semantics only, the look stays the same.\n * @default 'div'\n */\n titleTag = input<NextStepCardTitleTag>('div');\n\n /**\n * Layout of several actions. `inline`: side by side at the end of the card\n * (below 480 px card width in their own row, stretched). `stacked`: always\n * in their own row, one per line at equal width, 8 px apart (e.g. a list of\n * sign-in methods).\n * @default 'inline'\n */\n actionsLayout = input<NextStepCardActionsLayout>('inline');\n\n /**\n * Size of the built-in action buttons and link: `sm` (32 px, the 3.2\n * default), `md` (42 px) or `lg` (48.5 px, meets the 44 px touch target).\n * @default 'sm'\n */\n actionSize = input<NextStepCardActionSize>('sm');\n\n /**\n * Weight of the built-in actions. `default`: filled primary, ghost secondary\n * with a chevron, primary last. `equal`: two answers of the same weight, both\n * `outline` in the primary tone at `actionSize`, primary first, no chevron\n * (`primaryClick`, `secondaryClick`, `primaryLoading`, `primaryDisabled` and\n * `announce` work as before). Use `actionSize=\"lg\"` (48.5 px, the 44 px\n * target; a dev warning otherwise). No effect on `[slot='actions']`.\n * @default 'default'\n */\n actionsStyle = input<NextStepCardActionsStyle>('default');\n\n /** Message below the title. For markup, use the default slot instead. */\n message = input<string>();\n\n /** Label of the primary action. Omit to render no primary button. */\n primaryLabel = input<string>();\n\n /** Optional leading icon of the primary action. */\n primaryIcon = input<string>();\n\n /** Shows a spinner on the primary action. */\n primaryLoading = input<boolean>(false);\n\n /**\n * Disables the primary action. A disabled primary REQUIRES\n * `primaryDisabledReason`; disabled without a reason logs a dev warning.\n */\n primaryDisabled = input<boolean>(false);\n\n /** Why the primary action is disabled — shown as tooltip on the button. */\n primaryDisabledReason = input<string>('');\n\n /** Label of the ghost secondary action (rendered with a chevron). */\n secondaryLabel = input<string>();\n\n /**\n * Label of an external link rendered instead of the primary button\n * (opens in a new tab). Requires `linkHref`.\n */\n linkLabel = input<string>();\n\n /** Target URL of the external link. */\n linkHref = input<string>();\n\n /**\n * Where the link opens: `_blank` (a new tab, with `rel=\"noopener noreferrer\"`,\n * the 3.2 behaviour) or `_self` (the same tab, no `rel`).\n * @default '_blank'\n */\n linkTarget = input<'_blank' | '_self'>('_blank');\n\n /**\n * Announce title changes to screen readers via `aria-live=\"polite\"` —\n * for cards whose state advances in place.\n * @default false\n */\n announce = input<boolean>(false);\n\n /** Emits when the primary action is clicked. */\n primaryClick = output<void>();\n\n /** Emits when the secondary action is clicked. */\n secondaryClick = output<void>();\n\n private actionsSlot = viewChild<ElementRef<HTMLElement>>('actionsSlot');\n\n /**\n * Whether content was projected into `[slot='actions']` — projected actions\n * fully replace the built-in buttons.\n */\n protected hasProjectedActions = signal(false);\n\n protected cardClasses = computed(() =>\n [\n 'lc-next-step-card',\n `lc-next-step-card--${this.tone()}`,\n this.variant() === 'soft' ? 'lc-next-step-card--soft' : null,\n this.actionsLayout() === 'stacked' ? 'lc-next-step-card--actions-stacked' : null,\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n /** `rel` of the external link: only a new tab needs `noopener noreferrer`. */\n protected linkRel = computed(() => (this.linkTarget() === '_blank' ? 'noopener noreferrer' : null));\n\n protected showLink = computed(() => !!(this.linkLabel() && this.linkHref()));\n\n /**\n * Whether the card renders any action: hides the empty actions box, so the\n * narrow layout gets no empty row.\n */\n protected hasActions = computed(\n () =>\n this.hasProjectedActions() ||\n !!this.secondaryLabel() ||\n this.showLink() ||\n !!this.primaryLabel(),\n );\n\n constructor() {\n effect(() => {\n if (isDevMode() && this.primaryDisabled() && !this.primaryDisabledReason()) {\n console.warn(\n '[lc-next-step-card] `primaryDisabled` is set without a `primaryDisabledReason`. ' +\n 'A disabled primary action must explain why (shown as tooltip).',\n );\n }\n });\n effect(() => {\n if (isDevMode() && this.actionsStyle() === 'equal' && this.actionSize() !== 'lg') {\n console.warn('[lc-next-step-card] actionsStyle=\"equal\" needs actionSize=\"lg\" (44 px)');\n }\n });\n }\n\n ngAfterViewChecked(): void {\n // Projected [slot='actions'] content replaces the built-in buttons. The\n // signal write is equality-guarded, so unchanged checks are no-ops.\n const el = this.actionsSlot()?.nativeElement;\n this.hasProjectedActions.set(!!el && el.childElementCount > 0);\n }\n}\n","<div [class]=\"cardClasses()\" role=\"region\" [attr.aria-label]=\"cardTitle()\">\n <div class=\"lc-next-step-card__icon\" aria-hidden=\"true\">\n <lc-icon [name]=\"icon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n\n <div class=\"lc-next-step-card__body\">\n @switch (titleTag()) {\n @case ('h2') {\n <h2 class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </h2>\n }\n @case ('h3') {\n <h3 class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </h3>\n }\n @default {\n <div class=\"lc-next-step-card__title\" [attr.aria-live]=\"announce() ? 'polite' : null\">\n {{ cardTitle() }}\n </div>\n }\n }\n @if (message()) {\n <p class=\"lc-next-step-card__message\">{{ message() }}</p>\n }\n <div class=\"lc-next-step-card__custom-message\">\n <ng-content />\n </div>\n <!-- Form-safe body: no inherited message text styles. -->\n <div class=\"lc-next-step-card__content\">\n <ng-content select=\"[slot='body']\" />\n </div>\n <div class=\"lc-next-step-card__blockers\">\n <ng-content select=\"[slot='blockers']\" />\n </div>\n <div class=\"lc-next-step-card__meta\">\n <ng-content select=\"[slot='meta']\" />\n </div>\n </div>\n\n <div class=\"lc-next-step-card__actions\" [class.lc-next-step-card__actions--empty]=\"!hasActions()\">\n <div #actionsSlot class=\"lc-next-step-card__actions-slot\">\n <ng-content select=\"[slot='actions']\" />\n </div>\n @if (!hasProjectedActions() && actionsStyle() === 'equal') {\n <!-- Two answers of the same weight (3.16, BA-5): the primary first, both outline in the primary tone. -->\n @if (showLink()) {\n <a\n class=\"btn btn-secondary btn-{{ actionSize() }} lc-next-step-card__link\"\n [href]=\"linkHref()\"\n [attr.target]=\"linkTarget()\"\n [attr.rel]=\"linkRel()\"\n >\n <lc-icon name=\"external-link\" size=\"sm\" [decorative]=\"true\" />\n {{ linkLabel() }}\n </a>\n } @else if (primaryLabel()) {\n <lc-button\n variant=\"outline\"\n tone=\"primary\"\n [size]=\"actionSize()\"\n [loading]=\"primaryLoading()\"\n [disabled]=\"primaryDisabled()\"\n [disabledReason]=\"primaryDisabledReason()\"\n (clicked)=\"primaryClick.emit()\"\n >\n @if (primaryIcon()) {\n <lc-icon slot=\"icon-before\" [name]=\"primaryIcon()!\" size=\"sm\" [decorative]=\"true\" />\n }\n {{ primaryLabel() }}\n </lc-button>\n }\n @if (secondaryLabel()) {\n <lc-button variant=\"outline\" tone=\"primary\" [size]=\"actionSize()\" (clicked)=\"secondaryClick.emit()\">\n {{ secondaryLabel() }}\n </lc-button>\n }\n } @else if (!hasProjectedActions()) {\n @if (secondaryLabel()) {\n <lc-button variant=\"ghost\" [size]=\"actionSize()\" (clicked)=\"secondaryClick.emit()\">\n {{ secondaryLabel() }}\n <lc-icon slot=\"icon-after\" name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </lc-button>\n }\n @if (showLink()) {\n <a\n class=\"btn btn-secondary btn-{{ actionSize() }} lc-next-step-card__link\"\n [href]=\"linkHref()\"\n [attr.target]=\"linkTarget()\"\n [attr.rel]=\"linkRel()\"\n >\n <lc-icon name=\"external-link\" size=\"sm\" [decorative]=\"true\" />\n {{ linkLabel() }}\n </a>\n } @else if (primaryLabel()) {\n <lc-button\n variant=\"primary\"\n [size]=\"actionSize()\"\n [loading]=\"primaryLoading()\"\n [disabled]=\"primaryDisabled()\"\n [disabledReason]=\"primaryDisabledReason()\"\n (clicked)=\"primaryClick.emit()\"\n >\n @if (primaryIcon()) {\n <lc-icon slot=\"icon-before\" [name]=\"primaryIcon()!\" size=\"sm\" [decorative]=\"true\" />\n }\n {{ primaryLabel() }}\n </lc-button>\n }\n }\n </div>\n</div>\n"],"mappings":";;;;;AA2GA,IAAa,wBAAb,MAAa,sBAAqB;CAOhC,OAAO,MAAwB,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAWzC,UAAU,MAA2B,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG/C,OAAO,MAAc,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG1C,YAAY,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAQpC,WAAW,MAA4B,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAS7C,gBAAgB,MAAiC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAO1D,aAAa,MAA8B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAWhD,eAAe,MAAgC,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAGzD,UAAU,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGzB,eAAe,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,cAAc,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,iBAAiB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAMtC,kBAAkB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,wBAAwB,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,wBAAA,CAAA,+BAAA,CAAA,CAAA;CAGzC,iBAAiB,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAMhC,YAAY,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,WAAW,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAO1B,aAAa,MAA0B,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAOhD,WAAW,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGhC,eAAe,OAAM;CAGrB,iBAAiB,OAAM;CAEf,cAAc,UAAmC,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAM7D,sBAAsB,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAEnC,cAAc,eACtB;EACE;EACA,sBAAsB,KAAK,KAAI;EAC/B,KAAK,QAAO,MAAO,SAAS,4BAA4B;EACxD,KAAK,cAAa,MAAO,YAAY,uCAAuC;CAC7E,CAAA,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,GACZ,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGS,UAAU,eAAgB,KAAK,WAAU,MAAO,WAAW,wBAAwB,MAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEzF,WAAW,eAAe,CAAC,EAAE,KAAK,UAAS,KAAM,KAAK,SAAQ,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAMlE,aAAa,eAEnB,KAAK,oBAAmB,KACxB,CAAC,CAAC,KAAK,eAAc,KACrB,KAAK,SAAQ,KACb,CAAC,CAAC,KAAK,aAAY,GACtB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAED,cAAA;EACE,aAAY;GACV,IAAI,UAAS,KAAM,KAAK,gBAAe,KAAM,CAAC,KAAK,sBAAqB,GACtE,QAAQ,KACN,gJACkE;EAGxE,CAAC;EACD,aAAY;GACV,IAAI,UAAS,KAAM,KAAK,aAAY,MAAO,WAAW,KAAK,WAAU,MAAO,MAC1E,QAAQ,KAAK,4EAAwE;EAEzF,CAAC;CACH;CAEA,qBAAkB;EAGhB,MAAM,KAAK,KAAK,YAAW,CAAE,EAAE;EAC/B,KAAK,oBAAoB,IAAI,CAAC,CAAC,MAAM,GAAG,oBAAoB,CAAC;CAC/D;CA3KW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAqB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAArB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAqB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;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,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,uBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;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,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,cAAA;GAAA,gBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,aAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UC3GlC;EAiHA,QAAA,CAAA,47RAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDbY;GAAe,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;GAAA,SAAA;IAAA;IAAA;IAAA;GAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAAE;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eAEzB,CAAC,iBAAiB;GAAC;GAAyB;GAAoB;EAAkB,CAAC,CAAC;EAAC,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAKzF,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA;EAVjC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAmB,YACjB;GAAI,SACP,CAAC,iBAAiB,aAAa;GAAC,eAE1B,CAAC,iBAAiB;IAAC;IAAyB;IAAoB;GAAkB,CAAC,CAAC;GAAC,iBAGnF,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,47RAAA;EAAA,CAAA;CAoHU,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,MAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,MAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,uBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,cAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,gBAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,eAAa,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
|
|
@@ -355,7 +355,7 @@ var NotificationCenterComponent = class NotificationCenterComponent {
|
|
|
355
355
|
},
|
|
356
356
|
ngImport: i0,
|
|
357
357
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
358
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"],
|
|
358
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}.lc-nc__filter:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible,.lc-nc__action-btn:focus-visible,.lc-nc__filter:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"],
|
|
359
359
|
dependencies: [{
|
|
360
360
|
kind: "component",
|
|
361
361
|
type: IconComponent,
|
|
@@ -418,7 +418,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
418
418
|
])],
|
|
419
419
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
420
420
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
421
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"]
|
|
421
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}.lc-nc__filter:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible,.lc-nc__action-btn:focus-visible,.lc-nc__filter:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"]
|
|
422
422
|
}]
|
|
423
423
|
}],
|
|
424
424
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\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} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\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} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|