@lifecorner/angular-ui-kit 3.21.1 → 3.22.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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, DestroyRef, Injector, NgZone, ViewEncapsulation, afterNextRender, computed, effect, inject, input, output, signal, untracked, viewChild } from "@angular/core";
3
3
  import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
4
- import { ICON_TRASH } from "@lifecorner/angular-ui-kit/icons";
4
+ import { ICON_ARROW_DOWN, ICON_CLIPBOARD, ICON_PLAYER_PAUSE, ICON_PLAYER_PLAY, ICON_SEARCH, ICON_TRASH } from "@lifecorner/angular-ui-kit/icons";
5
5
  const ANSI_CLASSES = {
6
6
  "1": "ansi-bold",
7
7
  "3": "ansi-italic",
@@ -524,7 +524,14 @@ var LogViewerComponent = class LogViewerComponent {
524
524
  "strict"
525
525
  ]
526
526
  }],
527
- viewProviders: [ɵprovideKitIcons([ICON_TRASH])],
527
+ viewProviders: [ɵprovideKitIcons([
528
+ ICON_TRASH,
529
+ ICON_SEARCH,
530
+ ICON_PLAYER_PLAY,
531
+ ICON_PLAYER_PAUSE,
532
+ ICON_CLIPBOARD,
533
+ ICON_ARROW_DOWN
534
+ ])],
528
535
  changeDetection: i0.ChangeDetectionStrategy.OnPush,
529
536
  encapsulation: i0.ViewEncapsulation.None
530
537
  });
@@ -540,7 +547,14 @@ i0.ɵɵngDeclareClassMetadata({
540
547
  selector: "lc-log-viewer",
541
548
  standalone: true,
542
549
  imports: [IconComponent],
543
- viewProviders: [ɵprovideKitIcons([ICON_TRASH])],
550
+ viewProviders: [ɵprovideKitIcons([
551
+ ICON_TRASH,
552
+ ICON_SEARCH,
553
+ ICON_PLAYER_PLAY,
554
+ ICON_PLAYER_PAUSE,
555
+ ICON_CLIPBOARD,
556
+ ICON_ARROW_DOWN
557
+ ])],
544
558
  changeDetection: ChangeDetectionStrategy.OnPush,
545
559
  encapsulation: ViewEncapsulation.None,
546
560
  template: "<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",
@@ -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 { ICON_TRASH } 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: [ɵprovideKitIcons([ICON_TRASH])],\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\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":"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"}
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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\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":"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"}
@@ -2,7 +2,7 @@ import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, computed, effect, input, model, output, untracked, viewChild } from "@angular/core";
3
3
  import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
4
4
  import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
5
- import { ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
5
+ import { ICON_SEARCH, ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
6
6
  import { Subject, debounce, distinctUntilChanged, filter, of, timer } from "rxjs";
7
7
  let nextUniqueId = 0;
8
8
  var SearchInputComponent = class SearchInputComponent {
@@ -176,7 +176,7 @@ var SearchInputComponent = class SearchInputComponent {
176
176
  "strict"
177
177
  ]
178
178
  }],
179
- viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],
179
+ viewProviders: [ɵprovideKitIcons([ICON_X_MARK, ICON_SEARCH])],
180
180
  changeDetection: i0.ChangeDetectionStrategy.OnPush
181
181
  });
182
182
  };
@@ -191,7 +191,7 @@ i0.ɵɵngDeclareClassMetadata({
191
191
  selector: "lc-search-input",
192
192
  standalone: true,
193
193
  imports: [IconComponent],
194
- viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],
194
+ viewProviders: [ɵprovideKitIcons([ICON_X_MARK, ICON_SEARCH])],
195
195
  changeDetection: ChangeDetectionStrategy.OnPush,
196
196
  template: "<div [class]=\"wrapperClasses()\">\n <lc-icon name=\"magnifying-glass\" class=\"search-input__icon\" [decorative]=\"true\"></lc-icon>\n <input\n #searchInputEl\n type=\"search\"\n class=\"search-input__field\"\n [id]=\"inputId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n [attr.aria-label]=\"ariaLabelledBy() ? null : ariaLabel() || 'Search'\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n />\n @if (value()) {\n <button\n type=\"button\"\n class=\"search-input__clear\"\n (click)=\"clear()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n [disabled]=\"disabled()\"\n >\n <lc-icon name=\"x-mark\" class=\"search-input__clear-icon\" [decorative]=\"true\"></lc-icon>\n </button>\n }\n</div>\n",
197
197
  styles: [":host{display:block;width:100%}.search-input{display:flex;align-items:center;gap:.5rem;width:100%;background-color:var(--color-surface-2);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:border-color .15s ease,box-shadow .15s ease}.search-input:hover:not(.search-input--disabled){border-color:var(--color-text-secondary)}.search-input:focus-within{border-color:var(--color-primary-500);box-shadow:0 0 0 2px #20849733}.search-input--sm{padding:.375rem .625rem;font-size:.8125rem}.search-input--md{padding:.5rem .75rem;font-size:.875rem}.search-input--lg{padding:.625rem 1rem;font-size:1rem}.search-input--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.search-input__icon{flex-shrink:0;color:var(--color-text-tertiary);--icon-size: 1rem}.search-input__field{flex:1;border:none;outline:none;background:transparent;font:inherit;color:var(--color-text-primary);min-width:0}.search-input__field::placeholder{color:var(--color-text-tertiary)}.search-input__field::-webkit-search-cancel-button,.search-input__field::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.search-input__clear{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;transition:background .15s ease}.search-input__clear:hover{background:var(--color-border-strong)}.search-input__clear:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:1px}.search-input__clear-icon{--icon-size: .75rem;color:var(--color-text-secondary)}\n"]
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-search-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/search-input/search-input.component.ts","../../../../libs/angular-ui-kit/src/lib/search-input/search-input.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n effect,\n input,\n model,\n output,\n computed,\n ElementRef,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\nimport { Subject, debounce, distinctUntilChanged, filter, of, timer } from 'rxjs';\n\nexport type SearchInputSize = 'sm' | 'md' | 'lg';\n\n/** A value travelling through the debounce pipe. */\ninterface SearchEmission {\n readonly value: string;\n /** Bypass the debounce (clear button, external writes). */\n readonly immediate: boolean;\n /** Only update the dedupe baseline, never reach `searchChange` (external writes). */\n readonly silent: boolean;\n}\n\nlet nextUniqueId = 0;\n\n/**\n * Search input component with integrated search functionality.\n *\n * Features:\n * - Built-in search icon\n * - Clear button for resetting input\n * - RxJS-based configurable debounce\n * - Multiple size variants (sm, md, lg)\n * - Disabled state support\n * - Submit event on Enter key\n * - Two-way bindable `value` so a parent can preset or reset the query\n *\n * @example\n * ```html\n * <lc-search-input\n * placeholder=\"Search...\"\n * [debounceMs]=\"300\"\n * [(value)]=\"query\"\n * (searchChange)=\"onSearch($event)\"\n * ></lc-search-input>\n * ```\n */\n@Component({\n selector: 'lc-search-input',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],\n templateUrl: './search-input.component.html',\n styleUrls: ['./search-input.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SearchInputComponent {\n /**\n * id of the native input (lets a parent `<label for>` point at it).\n * @default generated\n */\n readonly inputId = input<string>(`lc-search-input-${++nextUniqueId}`);\n\n /**\n * Placeholder text.\n * @default 'Search…'\n */\n placeholder = input<string>('Search…');\n\n /**\n * Size variant.\n * @default 'md'\n */\n size = input<SearchInputSize>('md');\n\n /**\n * Debounce delay in milliseconds. 0 means no debounce.\n * @default 300\n */\n debounceMs = input<number>(300);\n\n /**\n * Whether the search input is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Accessible name of the input.\n * @default 'Search'\n */\n readonly ariaLabel = input<string>();\n\n /** id(s) of external element(s) labelling the input (wins over `ariaLabel`). */\n readonly ariaLabelledBy = input<string>();\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear search');\n\n /**\n * Current query (two-way bindable). Writing it from outside updates the\n * field without emitting `searchChange`; `valueChange` emits on every\n * keystroke (undebounced) and on clear.\n */\n readonly value = model<string>('');\n\n /**\n * Emitted when the search value changes (debounced).\n */\n readonly searchChange = output<string>();\n\n /**\n * Emitted when the user presses Enter.\n */\n readonly searchSubmit = output<string>();\n\n protected readonly inputRef = viewChild<ElementRef<HTMLInputElement>>('searchInputEl');\n\n protected wrapperClasses = computed(() => {\n return [\n 'search-input',\n `search-input--${this.size()}`,\n this.disabled() ? 'search-input--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n private readonly input$ = new Subject<SearchEmission>();\n /** Last value pushed into the pipe (by typing, clear or an external write). */\n private lastPushed = '';\n\n constructor() {\n this.input$\n .pipe(\n // debounceMs is read per emission, so changing it takes effect immediately.\n // 0 means \"no debounce\" — emit synchronously rather than on a 0ms timer.\n debounce(({ immediate }) => {\n const ms = this.debounceMs();\n return immediate || ms <= 0 ? of(null) : timer(ms);\n }),\n distinctUntilChanged((a, b) => a.value === b.value),\n filter(({ silent }) => !silent),\n takeUntilDestroyed(),\n )\n .subscribe(({ value }) => this.searchChange.emit(value));\n\n // A value written from outside is already known to the parent: run it through\n // the pipe silently so it becomes the dedupe baseline (and cancels a pending\n // debounce) without echoing a searchChange back.\n effect(() => {\n const value = this.value();\n untracked(() => {\n if (value !== this.lastPushed) {\n this.push(value, { immediate: true, silent: true });\n }\n });\n });\n }\n\n protected onInput(event: Event): void {\n const val = (event.target as HTMLInputElement).value;\n this.value.set(val);\n this.push(val, { immediate: false, silent: false });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n this.searchSubmit.emit(this.value());\n }\n }\n\n protected clear(): void {\n this.value.set('');\n // immediate: emits '' synchronously through the pipe, exactly once\n this.push('', { immediate: true, silent: false });\n this.inputRef()?.nativeElement.focus();\n }\n\n private push(value: string, opts: Omit<SearchEmission, 'value'>): void {\n this.lastPushed = value;\n this.input$.next({ value, ...opts });\n }\n}\n","<div [class]=\"wrapperClasses()\">\n <lc-icon name=\"magnifying-glass\" class=\"search-input__icon\" [decorative]=\"true\"></lc-icon>\n <input\n #searchInputEl\n type=\"search\"\n class=\"search-input__field\"\n [id]=\"inputId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n [attr.aria-label]=\"ariaLabelledBy() ? null : ariaLabel() || 'Search'\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n />\n @if (value()) {\n <button\n type=\"button\"\n class=\"search-input__clear\"\n (click)=\"clear()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n [disabled]=\"disabled()\"\n >\n <lc-icon name=\"x-mark\" class=\"search-input__clear-icon\" [decorative]=\"true\"></lc-icon>\n </button>\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-search-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/search-input/search-input.component.ts","../../../../libs/angular-ui-kit/src/lib/search-input/search-input.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n effect,\n input,\n model,\n output,\n computed,\n ElementRef,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_SEARCH, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\nimport { Subject, debounce, distinctUntilChanged, filter, of, timer } from 'rxjs';\n\nexport type SearchInputSize = 'sm' | 'md' | 'lg';\n\n/** A value travelling through the debounce pipe. */\ninterface SearchEmission {\n readonly value: string;\n /** Bypass the debounce (clear button, external writes). */\n readonly immediate: boolean;\n /** Only update the dedupe baseline, never reach `searchChange` (external writes). */\n readonly silent: boolean;\n}\n\nlet nextUniqueId = 0;\n\n/**\n * Search input component with integrated search functionality.\n *\n * Features:\n * - Built-in search icon\n * - Clear button for resetting input\n * - RxJS-based configurable debounce\n * - Multiple size variants (sm, md, lg)\n * - Disabled state support\n * - Submit event on Enter key\n * - Two-way bindable `value` so a parent can preset or reset the query\n *\n * @example\n * ```html\n * <lc-search-input\n * placeholder=\"Search...\"\n * [debounceMs]=\"300\"\n * [(value)]=\"query\"\n * (searchChange)=\"onSearch($event)\"\n * ></lc-search-input>\n * ```\n */\n@Component({\n selector: 'lc-search-input',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_X_MARK, ICON_SEARCH])],\n templateUrl: './search-input.component.html',\n styleUrls: ['./search-input.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SearchInputComponent {\n /**\n * id of the native input (lets a parent `<label for>` point at it).\n * @default generated\n */\n readonly inputId = input<string>(`lc-search-input-${++nextUniqueId}`);\n\n /**\n * Placeholder text.\n * @default 'Search…'\n */\n placeholder = input<string>('Search…');\n\n /**\n * Size variant.\n * @default 'md'\n */\n size = input<SearchInputSize>('md');\n\n /**\n * Debounce delay in milliseconds. 0 means no debounce.\n * @default 300\n */\n debounceMs = input<number>(300);\n\n /**\n * Whether the search input is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Accessible name of the input.\n * @default 'Search'\n */\n readonly ariaLabel = input<string>();\n\n /** id(s) of external element(s) labelling the input (wins over `ariaLabel`). */\n readonly ariaLabelledBy = input<string>();\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear search');\n\n /**\n * Current query (two-way bindable). Writing it from outside updates the\n * field without emitting `searchChange`; `valueChange` emits on every\n * keystroke (undebounced) and on clear.\n */\n readonly value = model<string>('');\n\n /**\n * Emitted when the search value changes (debounced).\n */\n readonly searchChange = output<string>();\n\n /**\n * Emitted when the user presses Enter.\n */\n readonly searchSubmit = output<string>();\n\n protected readonly inputRef = viewChild<ElementRef<HTMLInputElement>>('searchInputEl');\n\n protected wrapperClasses = computed(() => {\n return [\n 'search-input',\n `search-input--${this.size()}`,\n this.disabled() ? 'search-input--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n private readonly input$ = new Subject<SearchEmission>();\n /** Last value pushed into the pipe (by typing, clear or an external write). */\n private lastPushed = '';\n\n constructor() {\n this.input$\n .pipe(\n // debounceMs is read per emission, so changing it takes effect immediately.\n // 0 means \"no debounce\" — emit synchronously rather than on a 0ms timer.\n debounce(({ immediate }) => {\n const ms = this.debounceMs();\n return immediate || ms <= 0 ? of(null) : timer(ms);\n }),\n distinctUntilChanged((a, b) => a.value === b.value),\n filter(({ silent }) => !silent),\n takeUntilDestroyed(),\n )\n .subscribe(({ value }) => this.searchChange.emit(value));\n\n // A value written from outside is already known to the parent: run it through\n // the pipe silently so it becomes the dedupe baseline (and cancels a pending\n // debounce) without echoing a searchChange back.\n effect(() => {\n const value = this.value();\n untracked(() => {\n if (value !== this.lastPushed) {\n this.push(value, { immediate: true, silent: true });\n }\n });\n });\n }\n\n protected onInput(event: Event): void {\n const val = (event.target as HTMLInputElement).value;\n this.value.set(val);\n this.push(val, { immediate: false, silent: false });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n this.searchSubmit.emit(this.value());\n }\n }\n\n protected clear(): void {\n this.value.set('');\n // immediate: emits '' synchronously through the pipe, exactly once\n this.push('', { immediate: true, silent: false });\n this.inputRef()?.nativeElement.focus();\n }\n\n private push(value: string, opts: Omit<SearchEmission, 'value'>): void {\n this.lastPushed = value;\n this.input$.next({ value, ...opts });\n }\n}\n","<div [class]=\"wrapperClasses()\">\n <lc-icon name=\"magnifying-glass\" class=\"search-input__icon\" [decorative]=\"true\"></lc-icon>\n <input\n #searchInputEl\n type=\"search\"\n class=\"search-input__field\"\n [id]=\"inputId()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n [attr.aria-label]=\"ariaLabelledBy() ? null : ariaLabel() || 'Search'\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n />\n @if (value()) {\n <button\n type=\"button\"\n class=\"search-input__clear\"\n (click)=\"clear()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n [disabled]=\"disabled()\"\n >\n <lc-icon name=\"x-mark\" class=\"search-input__clear-icon\" [decorative]=\"true\"></lc-icon>\n </button>\n }\n</div>\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, ElementRef, computed, inject, input, model, output, signal } from "@angular/core";
3
3
  import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
4
- import { ICON_CHECK, ICON_CHEVRON_RIGHT, ICON_FILE, ICON_FILE_TEXT, ICON_KEY, ICON_SETTINGS } from "@lifecorner/angular-ui-kit/icons";
4
+ import { ICON_CHECK, ICON_CHEVRON_RIGHT, ICON_FILE, ICON_FILE_TEXT, ICON_KEY, ICON_LOCK, ICON_SETTINGS } from "@lifecorner/angular-ui-kit/icons";
5
5
  const FOLDER_ICON = "folder";
6
6
  const FOLDER_OPEN_ICON = "folder-open";
7
7
  const FILE_FALLBACK_ICON = "file";
@@ -409,6 +409,7 @@ var TreeViewComponent = class TreeViewComponent {
409
409
  ICON_FILE,
410
410
  ICON_FILE_TEXT,
411
411
  ICON_KEY,
412
+ ICON_LOCK,
412
413
  ICON_SETTINGS
413
414
  ])],
414
415
  changeDetection: i0.ChangeDetectionStrategy.OnPush
@@ -431,6 +432,7 @@ i0.ɵɵngDeclareClassMetadata({
431
432
  ICON_FILE,
432
433
  ICON_FILE_TEXT,
433
434
  ICON_KEY,
435
+ ICON_LOCK,
434
436
  ICON_SETTINGS
435
437
  ])],
436
438
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-tree-view.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/tree-view/file-icons.ts","../../../../libs/angular-ui-kit/src/lib/tree-view/tree-view.component.ts","../../../../libs/angular-ui-kit/src/lib/tree-view/tree-view.component.html"],"sourcesContent":["/**\n * File-type → icon mapping for the TreeView component.\n *\n * Icon names refer to Tabler Icons (the design system's icon source).\n * Resolution order for a file node:\n * 1. explicit `node.icon`\n * 2. exact file name match (e.g. `package.json`, `Dockerfile`)\n * 3. file extension match (e.g. `.ts`, `.png`)\n * 4. generic file fallback\n */\n\n/** Icon used for collapsed folders. */\nexport const FOLDER_ICON = 'folder';\n/** Icon used for expanded (open) folders. */\nexport const FOLDER_OPEN_ICON = 'folder-open';\n/** Generic fallback icon for files with no specific match. */\nexport const FILE_FALLBACK_ICON = 'file';\n\n/** Map of well-known file names (lower-cased) to icons. */\nconst FILE_NAME_ICONS: Record<string, string> = {\n 'package.json': 'brand-npm',\n 'package-lock.json': 'brand-npm',\n 'yarn.lock': 'brand-yarn',\n 'pnpm-lock.yaml': 'brand-pnpm',\n 'dockerfile': 'brand-docker',\n 'docker-compose.yml': 'brand-docker',\n 'docker-compose.yaml': 'brand-docker',\n '.gitignore': 'brand-git',\n '.gitattributes': 'brand-git',\n '.gitmodules': 'brand-git',\n 'readme.md': 'book',\n 'license': 'license',\n 'license.md': 'license',\n '.env': 'key',\n '.env.local': 'key',\n '.editorconfig': 'settings',\n '.prettierrc': 'settings',\n '.eslintrc': 'settings',\n 'tsconfig.json': 'brand-typescript',\n 'angular.json': 'brand-angular',\n 'nx.json': 'settings',\n 'makefile': 'terminal',\n};\n\n/** Map of file extensions (without dot, lower-cased) to icons. */\nconst FILE_EXT_ICONS: Record<string, string> = {\n // Languages\n ts: 'brand-typescript',\n tsx: 'brand-typescript',\n js: 'brand-javascript',\n jsx: 'brand-javascript',\n mjs: 'brand-javascript',\n cjs: 'brand-javascript',\n html: 'brand-html5',\n htm: 'brand-html5',\n css: 'brand-css3',\n scss: 'brand-sass',\n sass: 'brand-sass',\n less: 'brand-css3',\n py: 'brand-python',\n rb: 'diamond',\n go: 'brand-golang',\n rs: 'brand-rust',\n java: 'coffee',\n kt: 'brand-kotlin',\n swift: 'brand-swift',\n c: 'file-code',\n h: 'file-code',\n cpp: 'brand-cpp',\n cs: 'brand-c-sharp',\n php: 'brand-php',\n dart: 'brand-flutter',\n sh: 'terminal-2',\n bash: 'terminal-2',\n zsh: 'terminal-2',\n sql: 'database',\n // Data / config\n json: 'braces',\n jsonc: 'braces',\n yml: 'file-code',\n yaml: 'file-code',\n toml: 'file-code',\n xml: 'file-code',\n ini: 'settings',\n env: 'key',\n // Docs\n md: 'markdown',\n mdx: 'markdown',\n txt: 'file-text',\n pdf: 'file-type-pdf',\n doc: 'file-text',\n docx: 'file-text',\n csv: 'file-spreadsheet',\n xls: 'file-spreadsheet',\n xlsx: 'file-spreadsheet',\n // Images\n png: 'photo',\n jpg: 'photo',\n jpeg: 'photo',\n gif: 'photo',\n webp: 'photo',\n svg: 'file-vector',\n ico: 'photo',\n bmp: 'photo',\n // Media\n mp3: 'music',\n wav: 'music',\n mp4: 'video',\n mov: 'video',\n webm: 'video',\n // Archives\n zip: 'file-zip',\n tar: 'file-zip',\n gz: 'file-zip',\n rar: 'file-zip',\n '7z': 'file-zip',\n // Fonts\n woff: 'typography',\n woff2: 'typography',\n ttf: 'typography',\n otf: 'typography',\n // Misc\n lock: 'lock',\n};\n\n/**\n * Resolve the icon name for a file based on its name.\n * Performs exact-name then extension lookup, falling back to a generic file icon.\n */\nexport function resolveFileIcon(fileName: string): string {\n const lower = fileName.toLowerCase();\n\n const byName = FILE_NAME_ICONS[lower];\n if (byName) return byName;\n\n const dotIndex = lower.lastIndexOf('.');\n if (dotIndex > 0 && dotIndex < lower.length - 1) {\n const ext = lower.slice(dotIndex + 1);\n const byExt = FILE_EXT_ICONS[ext];\n if (byExt) return byExt;\n }\n\n return FILE_FALLBACK_ICON;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK,\n ICON_CHEVRON_RIGHT,\n ICON_FILE,\n ICON_FILE_TEXT,\n ICON_KEY,\n ICON_SETTINGS,\n} from '@lifecorner/angular-ui-kit/icons';\nimport {\n FILE_FALLBACK_ICON,\n FOLDER_ICON,\n FOLDER_OPEN_ICON,\n resolveFileIcon,\n} from './file-icons';\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\n/**\n * Node type. `'file'` and `'folder'` get the built-in file-tree treatment\n * (extension icons, folder open/close icons). Any other string marks a custom\n * domain type: such nodes render a colored type dot (or an explicit `icon`)\n * instead of file icons.\n */\nexport type TreeNodeType = 'file' | 'folder' | (string & {});\n\nexport type TreeNodeStatus =\n | 'default'\n | 'added'\n | 'modified'\n | 'removed'\n | 'muted'\n | 'success'\n | 'busy';\n\n/**\n * A node in the tree. Any node carrying `children` can expand — this covers\n * file trees (folders) as well as arbitrary domain hierarchies.\n */\nexport interface TreeNode {\n /** Display name, e.g. `app.component.ts` or `src`. */\n name: string;\n /**\n * Node type. Optional — inferred as `folder` when `children` is present,\n * otherwise `file`. May be any string for domain hierarchies.\n */\n type?: TreeNodeType;\n /**\n * Stable identifier. Optional — when omitted, the full path is used,\n * which is stable as long as names are unique among siblings.\n */\n id?: string;\n /** Child nodes. Any node with children can expand / collapse. */\n children?: TreeNode[];\n /**\n * Explicit Tabler icon name. Overrides automatic file-type / folder\n * icon resolution and the custom-type dot.\n */\n icon?: string;\n /**\n * Accent color for the node's icon / type dot. Any CSS color, including\n * `var(--…)` token references.\n */\n color?: string;\n /** Optional badge text shown to the right of the node (e.g. git status, count). */\n badge?: string;\n /**\n * Tone of the badge / node accent. `success` additionally renders a ✓\n * indicator, `busy` a pulsing dot (static under `prefers-reduced-motion`).\n */\n status?: TreeNodeStatus;\n /** Whether this node starts expanded. Ignored for nodes without children. */\n expanded?: boolean;\n /** Disable selection / interaction for this node. */\n disabled?: boolean;\n}\n\n// ── Internal flattened representation ─────────────────────────────────────────\n\ninterface FlatNode {\n id: string;\n name: string;\n type: TreeNodeType;\n depth: number;\n hasChildren: boolean;\n expanded: boolean;\n /** Resolved icon, or `null` for custom-type nodes that render a type dot. */\n icon: string | null;\n color?: string;\n badge?: string;\n status: TreeNodeStatus;\n disabled: boolean;\n /** Whether each ancestor level still has following siblings (for guide lines). */\n ancestorHasSibling: boolean[];\n /** Whether this node is the last among its siblings. */\n isLast: boolean;\n /** 1-based position among its siblings (aria-posinset). */\n posInSet: number;\n /** Number of siblings including itself (aria-setsize). */\n setSize: number;\n /** Id of the parent node, `null` for root nodes. */\n parentId: string | null;\n}\n\nconst BADGE_STATUS_COLOR: Record<TreeNodeStatus, string> = {\n default: 'var(--color-text-tertiary)',\n added: 'var(--color-success-default, #16a34a)',\n modified: 'var(--color-warning-default, #d97706)',\n removed: 'var(--color-error-default, #dc2626)',\n muted: 'var(--color-text-disabled)',\n success: 'var(--color-success-default, #16a34a)',\n busy: 'var(--color-primary-500, #6366f1)',\n};\n\nfunction nodeType(node: TreeNode): TreeNodeType {\n return node.type ?? (node.children ? 'folder' : 'file');\n}\n\nfunction isExpandable(node: TreeNode): boolean {\n return !!node.children?.length;\n}\n\n/**\n * Tree view component for visualizing hierarchies — file / folder trees as\n * well as arbitrary domain object trees with custom node types.\n *\n * Features:\n * - Recursive rendering from a single `nodes` input; any node with children\n * can expand / collapse\n * - Automatic file-type icons by extension and well-known file name,\n * with open / closed folder icons (for `file` / `folder` nodes)\n * - Custom node types: colored type dots or explicit icons via `type`,\n * `color`, `icon`\n * - Expand / collapse, with expand-all / collapse-all helpers\n * - Two-way bound selection and a `nodeClick` event\n * - Indentation guide lines for readability\n * - Optional per-node status badges (added / modified / removed) and status\n * indicators (`success` ✓, `busy` pulse)\n * - Keyboard accessible: roving tabindex (one tab stop), Arrow keys / Home /\n * End move focus, Enter / Space toggle or select, Left / Right collapse /\n * expand or move to parent / first child\n * - Dark / light theme support via design tokens\n *\n * @example\n * ```html\n * <lc-tree-view [nodes]=\"projectTree\" [(selectedId)]=\"selected\" />\n * ```\n */\n@Component({\n selector: 'lc-tree-view',\n standalone: true,\n imports: [IconComponent],\n // Its own glyphs and the inline ones of the built-in file-type map (file-icons.ts).\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT, ICON_CHECK, ICON_FILE, ICON_FILE_TEXT, ICON_KEY, ICON_SETTINGS])],\n templateUrl: './tree-view.component.html',\n styleUrls: ['./tree-view.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'lc-tree-view' },\n})\nexport class TreeViewComponent {\n /** The root-level nodes of the tree. */\n readonly nodes = input.required<TreeNode[]>();\n\n /** Id (or path) of the currently selected node (two-way bindable). */\n readonly selectedId = model<string | null>(null);\n\n /** Show indentation guide lines. */\n readonly showGuides = input(true);\n\n /** Show the expand-all / collapse-all toolbar. */\n readonly showToolbar = input(true);\n\n /** Label of the toolbar's expand-all button. */\n readonly expandAllLabel = input('Expand all');\n\n /** Label of the toolbar's collapse-all button. */\n readonly collapseAllLabel = input('Collapse all');\n\n /** Icon size for node icons. */\n readonly iconSize = input<'xs' | 'sm' | 'md'>('sm');\n\n /** Accessible name of a folder's expand toggle. */\n readonly expandAriaLabel = input('Expand');\n\n /** Accessible name of a folder's collapse toggle. */\n readonly collapseAriaLabel = input('Collapse');\n\n /** Emitted when a node is clicked / activated. */\n readonly nodeClick = output<TreeNode>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Set of node ids the user has explicitly expanded / collapsed. */\n private readonly expandOverrides = signal<Map<string, boolean>>(new Map());\n\n /** Id of the node that last had keyboard focus (roving tabindex). */\n private readonly focusedId = signal<string | null>(null);\n\n /** Flattened, render-ready list of visible nodes. */\n protected readonly visibleNodes = computed<FlatNode[]>(() => {\n const out: FlatNode[] = [];\n const overrides = this.expandOverrides();\n this.flatten(this.nodes(), 0, '', [], overrides, out, null);\n return out;\n });\n\n /**\n * The single tab stop of the tree: the last focused node if it is still\n * visible, else the selected node, else the first enabled node.\n */\n protected readonly tabStopId = computed<string | null>(() => {\n const nodes = this.visibleNodes();\n const focusable = (id: string | null) =>\n id !== null && nodes.some((n) => n.id === id && !n.disabled);\n const focused = this.focusedId();\n if (focusable(focused)) return focused;\n const selected = this.selectedId();\n if (focusable(selected)) return selected;\n return nodes.find((n) => !n.disabled)?.id ?? null;\n });\n\n protected badgeColor(status: TreeNodeStatus): string {\n return BADGE_STATUS_COLOR[status];\n }\n\n protected onNodeClick(flat: FlatNode): void {\n if (flat.disabled) return;\n if (flat.hasChildren) {\n this.toggle(flat);\n }\n this.selectedId.set(flat.id);\n const original = this.findNode(this.nodes(), '', flat.id);\n if (original) this.nodeClick.emit(original);\n }\n\n protected onToggleClick(flat: FlatNode, event: Event): void {\n event.stopPropagation();\n if (!flat.disabled) this.toggle(flat);\n }\n\n protected onFocus(flat: FlatNode): void {\n this.focusedId.set(flat.id);\n }\n\n protected onKeydown(flat: FlatNode, event: KeyboardEvent): void {\n if (flat.disabled) return;\n const nodes = this.visibleNodes();\n const index = nodes.findIndex((n) => n.id === flat.id);\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n event.preventDefault();\n this.onNodeClick(flat);\n break;\n case 'ArrowRight':\n event.preventDefault();\n if (!flat.hasChildren) return;\n if (!flat.expanded) {\n this.setExpanded(flat.id, true);\n } else {\n // Expanded: move to the first child.\n this.focusIndex(index + 1);\n }\n break;\n case 'ArrowLeft':\n event.preventDefault();\n if (flat.hasChildren && flat.expanded) {\n this.setExpanded(flat.id, false);\n } else if (flat.parentId !== null) {\n // Leaf or collapsed node: move to the parent.\n this.focusIndex(nodes.findIndex((n) => n.id === flat.parentId));\n }\n break;\n case 'ArrowDown':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, index, 1));\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, index, -1));\n break;\n case 'Home':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, -1, 1));\n break;\n case 'End':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, nodes.length, -1));\n break;\n }\n }\n\n /** Expand every folder in the tree. */\n expandAll(): void {\n this.setAll(true);\n }\n\n /** Collapse every folder in the tree. */\n collapseAll(): void {\n this.setAll(false);\n }\n\n protected trackById = (_: number, n: FlatNode): string => n.id;\n\n // ── internals ──────────────────────────────────────────────────────────────\n\n /** Index of the next enabled node from `from` in direction `dir`, or -1. */\n private nextEnabled(nodes: FlatNode[], from: number, dir: 1 | -1): number {\n for (let i = from + dir; i >= 0 && i < nodes.length; i += dir) {\n if (!nodes[i].disabled) return i;\n }\n return -1;\n }\n\n /**\n * Moves keyboard focus to the visible node at `index`. The rows are\n * rendered in `visibleNodes()` order, so the index maps 1:1 to the DOM.\n */\n private focusIndex(index: number): void {\n const nodes = this.visibleNodes();\n if (index < 0 || index >= nodes.length || nodes[index].disabled) return;\n this.focusedId.set(nodes[index].id);\n const items = this.host.nativeElement.querySelectorAll<HTMLElement>('.lc-tree-view__item');\n items[index]?.focus();\n }\n\n private toggle(flat: FlatNode): void {\n this.setExpanded(flat.id, !flat.expanded);\n }\n\n private setExpanded(id: string, value: boolean): void {\n this.expandOverrides.update((map) => {\n const next = new Map(map);\n next.set(id, value);\n return next;\n });\n }\n\n private setAll(value: boolean): void {\n const next = new Map<string, boolean>();\n const walk = (nodes: TreeNode[], parentPath: string): void => {\n for (const node of nodes) {\n const id = node.id ?? `${parentPath}/${node.name}`;\n if (node.children?.length) {\n next.set(id, value);\n walk(node.children, id);\n }\n }\n };\n walk(this.nodes(), '');\n this.expandOverrides.set(next);\n }\n\n private isExpanded(\n id: string,\n node: TreeNode,\n overrides: Map<string, boolean>,\n ): boolean {\n const override = overrides.get(id);\n if (override !== undefined) return override;\n return node.expanded ?? false;\n }\n\n private flatten(\n nodes: TreeNode[],\n depth: number,\n parentPath: string,\n ancestorHasSibling: boolean[],\n overrides: Map<string, boolean>,\n out: FlatNode[],\n parentId: string | null,\n ): void {\n nodes.forEach((node, index) => {\n const id = node.id ?? `${parentPath}/${node.name}`;\n const type = nodeType(node);\n const hasChildren = isExpandable(node);\n const expanded = hasChildren && this.isExpanded(id, node, overrides);\n const isLast = index === nodes.length - 1;\n\n out.push({\n id,\n name: node.name,\n type,\n depth,\n hasChildren,\n expanded,\n icon: this.resolveIcon(node, type, expanded),\n color: node.color,\n badge: node.badge,\n status: node.status ?? 'default',\n disabled: node.disabled ?? false,\n ancestorHasSibling,\n isLast,\n posInSet: index + 1,\n setSize: nodes.length,\n parentId,\n });\n\n if (expanded && node.children) {\n this.flatten(\n node.children,\n depth + 1,\n id,\n [...ancestorHasSibling, !isLast],\n overrides,\n out,\n id,\n );\n }\n });\n }\n\n private resolveIcon(\n node: TreeNode,\n type: TreeNodeType,\n expanded: boolean,\n ): string | null {\n if (node.icon) return node.icon;\n if (type === 'folder') return expanded ? FOLDER_OPEN_ICON : FOLDER_ICON;\n if (type === 'file') {\n return node.name ? resolveFileIcon(node.name) : FILE_FALLBACK_ICON;\n }\n // Custom domain type without explicit icon: render a colored type dot.\n return null;\n }\n\n private findNode(\n nodes: TreeNode[],\n parentPath: string,\n targetId: string,\n ): TreeNode | null {\n for (const node of nodes) {\n const id = node.id ?? `${parentPath}/${node.name}`;\n if (id === targetId) return node;\n if (node.children) {\n const found = this.findNode(node.children, id, targetId);\n if (found) return found;\n }\n }\n return null;\n }\n}\n","@if (showToolbar()) {\n <div class=\"lc-tree-view__toolbar\">\n <button type=\"button\" class=\"lc-tree-view__tool-btn\" (click)=\"expandAll()\" [attr.title]=\"expandAllLabel()\">\n <lc-icon name=\"fold-down\" size=\"xs\" [decorative]=\"true\" />\n <span>{{ expandAllLabel() }}</span>\n </button>\n <button type=\"button\" class=\"lc-tree-view__tool-btn\" (click)=\"collapseAll()\" [attr.title]=\"collapseAllLabel()\">\n <lc-icon name=\"fold-up\" size=\"xs\" [decorative]=\"true\" />\n <span>{{ collapseAllLabel() }}</span>\n </button>\n </div>\n}\n\n<ul class=\"lc-tree-view__list\" role=\"tree\">\n @for (node of visibleNodes(); track trackById($index, node)) {\n <li\n class=\"lc-tree-view__item\"\n [class.lc-tree-view__item--selected]=\"node.id === selectedId()\"\n [class.lc-tree-view__item--disabled]=\"node.disabled\"\n [class.lc-tree-view__item--folder]=\"node.type === 'folder'\"\n role=\"treeitem\"\n [attr.aria-level]=\"node.depth + 1\"\n [attr.aria-posinset]=\"node.posInSet\"\n [attr.aria-setsize]=\"node.setSize\"\n [attr.aria-expanded]=\"node.hasChildren ? node.expanded : null\"\n [attr.aria-selected]=\"node.id === selectedId()\"\n [attr.aria-disabled]=\"node.disabled ? true : null\"\n [attr.aria-busy]=\"node.status === 'busy' ? true : null\"\n [tabindex]=\"node.id === tabStopId() ? 0 : -1\"\n (focus)=\"onFocus(node)\"\n (click)=\"onNodeClick(node)\"\n (keydown)=\"onKeydown(node, $event)\"\n >\n <!-- indentation + guide lines -->\n @for (hasSibling of node.ancestorHasSibling; track $index) {\n <span\n class=\"lc-tree-view__indent\"\n [class.lc-tree-view__indent--guide]=\"showGuides() && hasSibling\"\n ></span>\n }\n\n <!-- expand / collapse chevron -->\n <span class=\"lc-tree-view__chevron\">\n @if (node.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-tree-view__chevron-btn\"\n [class.lc-tree-view__chevron-btn--expanded]=\"node.expanded\"\n (click)=\"onToggleClick(node, $event)\"\n [attr.aria-label]=\"node.expanded ? collapseAriaLabel() : expandAriaLabel()\"\n tabindex=\"-1\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n\n <!-- type / file icon, or type dot for custom node types -->\n <span\n class=\"lc-tree-view__icon\"\n [style.color]=\"\n node.color ?? (node.status === 'default' ? null : badgeColor(node.status))\n \"\n >\n @if (node.icon; as icon) {\n <lc-icon [name]=\"icon\" [size]=\"iconSize()\" [decorative]=\"true\" />\n } @else {\n <span\n class=\"lc-tree-view__type-dot\"\n [style.background-color]=\"node.color ?? null\"\n ></span>\n }\n </span>\n\n <!-- label -->\n <span class=\"lc-tree-view__label\">{{ node.name }}</span>\n\n <!-- badge -->\n @if (node.badge) {\n <span\n class=\"lc-tree-view__badge\"\n [style.color]=\"badgeColor(node.status)\"\n [style.border-color]=\"badgeColor(node.status)\"\n >{{ node.badge }}</span\n >\n }\n\n <!-- status indicator -->\n @if (node.status === 'success') {\n <span class=\"lc-tree-view__status lc-tree-view__status--success\">\n <lc-icon name=\"check\" size=\"xs\" [decorative]=\"true\" />\n </span>\n } @else if (node.status === 'busy') {\n <span class=\"lc-tree-view__status lc-tree-view__status--busy\"></span>\n }\n </li>\n }\n</ul>\n"],"mappings":";;;;AAYA,MAAa,cAAc;AAE3B,MAAa,mBAAmB;AAEhC,MAAa,qBAAqB;AAGlC,MAAM,kBAA0C;CAC9C,gBAAgB;CAChB,qBAAqB;CACrB,aAAa;CACb,kBAAkB;CAClB,cAAc;CACd,sBAAsB;CACtB,uBAAuB;CACvB,cAAc;CACd,kBAAkB;CAClB,eAAe;CACf,aAAa;CACb,WAAW;CACX,cAAc;CACd,QAAQ;CACR,cAAc;CACd,iBAAiB;CACjB,eAAe;CACf,aAAa;CACb,iBAAiB;CACjB,gBAAgB;CAChB,WAAW;CACX,YAAY;AACb;AAGD,MAAM,iBAAyC;CAE7C,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,IAAI;CACJ,OAAO;CACP,GAAG;CACH,GAAG;CACH,KAAK;CACL,IAAI;CACJ,KAAK;CACL,MAAM;CACN,IAAI;CACJ,MAAM;CACN,KAAK;CACL,KAAK;CAEL,MAAM;CACN,OAAO;CACP,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CAEL,IAAI;CACJ,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,MAAM;CAEN,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CAEL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CAEN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,KAAK;CACL,MAAM;CAEN,MAAM;CACN,OAAO;CACP,KAAK;CACL,KAAK;CAEL,MAAM;AACP;AAMD,SAAgB,gBAAgB,UAAgB;CAC9C,MAAM,QAAQ,SAAS,YAAW;CAElC,MAAM,SAAS,gBAAgB;CAC/B,IAAI,QAAQ,OAAO;CAEnB,MAAM,WAAW,MAAM,YAAY,GAAG;CACtC,IAAI,WAAW,KAAK,WAAW,MAAM,SAAS,GAAG;EAC/C,MAAM,MAAM,MAAM,MAAM,WAAW,CAAC;EACpC,MAAM,QAAQ,eAAe;EAC7B,IAAI,OAAO,OAAO;CACpB;CAEA,OAAO;AACT;AC5BA,MAAM,qBAAqD;CACzD,SAAS;CACT,OAAO;CACP,UAAU;CACV,SAAS;CACT,OAAO;CACP,SAAS;CACT,MAAM;AACP;AAED,SAAS,SAAS,MAAc;CAC9B,OAAO,KAAK,SAAS,KAAK,WAAW,WAAW;AAClD;AAEA,SAAS,aAAa,MAAc;CAClC,OAAO,CAAC,CAAC,KAAK,UAAU;AAC1B;AAuCA,IAAa,oBAAb,MAAa,kBAAiB;CAEnB,QAAQ,MAAM,SAAsB,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGpC,aAAa,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,aAAa,MAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,cAAc,MAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGzB,iBAAiB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpC,mBAAmB,MAAM,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,WAAW,MAA0B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAG1C,kBAAkB,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,oBAAoB,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,YAAY,OAAM;CAEV,OAAO,OAAgC,UAAU;CAGjD,kBAAkB,uBAA6B,IAAI,IAAG,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGzD,YAAY,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,eAAe,eAA0B;EAC1D,MAAM,MAAkB,CAAA;EACxB,MAAM,YAAY,KAAK,gBAAe;EACtC,KAAK,QAAQ,KAAK,MAAK,GAAI,GAAG,IAAI,CAAA,GAAI,WAAW,KAAK,IAAI;EAC1D,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAMiB,YAAY,eAA6B;EAC1D,MAAM,QAAQ,KAAK,aAAY;EAC/B,MAAM,aAAa,OACjB,OAAO,QAAQ,MAAM,MAAM,MAAM,EAAE,OAAO,MAAM,CAAC,EAAE,QAAQ;EAC7D,MAAM,UAAU,KAAK,UAAS;EAC9B,IAAI,UAAU,OAAO,GAAG,OAAO;EAC/B,MAAM,WAAW,KAAK,WAAU;EAChC,IAAI,UAAU,QAAQ,GAAG,OAAO;EAChC,OAAO,MAAM,MAAM,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM;CAC/C,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,WAAW,QAAsB;EACzC,OAAO,mBAAmB;CAC5B;CAEU,YAAY,MAAc;EAClC,IAAI,KAAK,UAAU;EACnB,IAAI,KAAK,aACP,KAAK,OAAO,IAAI;EAElB,KAAK,WAAW,IAAI,KAAK,EAAE;EAC3B,MAAM,WAAW,KAAK,SAAS,KAAK,MAAK,GAAI,IAAI,KAAK,EAAE;EACxD,IAAI,UAAU,KAAK,UAAU,KAAK,QAAQ;CAC5C;CAEU,cAAc,MAAgB,OAAY;EAClD,MAAM,gBAAe;EACrB,IAAI,CAAC,KAAK,UAAU,KAAK,OAAO,IAAI;CACtC;CAEU,QAAQ,MAAc;EAC9B,KAAK,UAAU,IAAI,KAAK,EAAE;CAC5B;CAEU,UAAU,MAAgB,OAAoB;EACtD,IAAI,KAAK,UAAU;EACnB,MAAM,QAAQ,KAAK,aAAY;EAC/B,MAAM,QAAQ,MAAM,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE;EAErD,QAAQ,MAAM,KAAd;GACE,KAAK;GACL,KAAK;IACH,MAAM,eAAc;IACpB,KAAK,YAAY,IAAI;IACrB;GACF,KAAK;IACH,MAAM,eAAc;IACpB,IAAI,CAAC,KAAK,aAAa;IACvB,IAAI,CAAC,KAAK,UACR,KAAK,YAAY,KAAK,IAAI,IAAI;SAG9B,KAAK,WAAW,QAAQ,CAAC;IAE3B;GACF,KAAK;IACH,MAAM,eAAc;IACpB,IAAI,KAAK,eAAe,KAAK,UAC3B,KAAK,YAAY,KAAK,IAAI,KAAK;SAC1B,IAAI,KAAK,aAAa,MAE3B,KAAK,WAAW,MAAM,WAAW,MAAM,EAAE,OAAO,KAAK,QAAQ,CAAC;IAEhE;GACF,KAAK;IACH,MAAM,eAAc;IACpB,KAAK,WAAW,KAAK,YAAY,OAAO,OAAO,CAAC,CAAC;IACjD;GACF,KAAK;IACH,MAAM,eAAc;IACpB,KAAK,WAAW,KAAK,YAAY,OAAO,OAAO,EAAE,CAAC;IAClD;GACF,KAAK;IACH,MAAM,eAAc;IACpB,KAAK,WAAW,KAAK,YAAY,OAAO,IAAI,CAAC,CAAC;IAC9C;GACF,KAAK;IACH,MAAM,eAAc;IACpB,KAAK,WAAW,KAAK,YAAY,OAAO,MAAM,QAAQ,EAAE,CAAC;EAE7D;CACF;CAGA,YAAS;EACP,KAAK,OAAO,IAAI;CAClB;CAGA,cAAW;EACT,KAAK,OAAO,KAAK;CACnB;CAEU,aAAa,GAAW,MAAwB,EAAE;CAKpD,YAAY,OAAmB,MAAc,KAAW;EAC9D,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,IAAI,MAAM,QAAQ,KAAK,KACxD,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,OAAO;EAEjC,OAAO;CACT;CAMQ,WAAW,OAAa;EAC9B,MAAM,QAAQ,KAAK,aAAY;EAC/B,IAAI,QAAQ,KAAK,SAAS,MAAM,UAAU,MAAM,MAAM,CAAC,UAAU;EACjE,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC,EAAE;EAElC,AADc,KAAK,KAAK,cAAc,iBAA8B,qBACpE,CAAA,CAAM,MAAM,EAAE,MAAK;CACrB;CAEQ,OAAO,MAAc;EAC3B,KAAK,YAAY,KAAK,IAAI,CAAC,KAAK,QAAQ;CAC1C;CAEQ,YAAY,IAAY,OAAc;EAC5C,KAAK,gBAAgB,QAAQ,QAAO;GAClC,MAAM,OAAO,IAAI,IAAI,GAAG;GACxB,KAAK,IAAI,IAAI,KAAK;GAClB,OAAO;EACT,CAAC;CACH;CAEQ,OAAO,OAAc;EAC3B,MAAM,uBAAO,IAAI,IAAG;EACpB,MAAM,QAAQ,OAAmB,eAA4B;GAC3D,KAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,KAAK,KAAK,MAAM,GAAG,WAAU,GAAI,KAAK;IAC5C,IAAI,KAAK,UAAU,QAAQ;KACzB,KAAK,IAAI,IAAI,KAAK;KAClB,KAAK,KAAK,UAAU,EAAE;IACxB;GACF;EACF;EACA,KAAK,KAAK,MAAK,GAAI,EAAE;EACrB,KAAK,gBAAgB,IAAI,IAAI;CAC/B;CAEQ,WACN,IACA,MACA,WAA+B;EAE/B,MAAM,WAAW,UAAU,IAAI,EAAE;EACjC,IAAI,aAAa,QAAW,OAAO;EACnC,OAAO,KAAK,YAAY;CAC1B;CAEQ,QACN,OACA,OACA,YACA,oBACA,WACA,KACA,UAAuB;EAEvB,MAAM,SAAS,MAAM,UAAS;GAC5B,MAAM,KAAK,KAAK,MAAM,GAAG,WAAU,GAAI,KAAK;GAC5C,MAAM,OAAO,SAAS,IAAI;GAC1B,MAAM,cAAc,aAAa,IAAI;GACrC,MAAM,WAAW,eAAe,KAAK,WAAW,IAAI,MAAM,SAAS;GACnE,MAAM,SAAS,UAAU,MAAM,SAAS;GAExC,IAAI,KAAK;IACP;IACA,MAAM,KAAK;IACX;IACA;IACA;IACA;IACA,MAAM,KAAK,YAAY,MAAM,MAAM,QAAQ;IAC3C,OAAO,KAAK;IACZ,OAAO,KAAK;IACZ,QAAQ,KAAK,UAAU;IACvB,UAAU,KAAK,YAAY;IAC3B;IACA;IACA,UAAU,QAAQ;IAClB,SAAS,MAAM;IACf;GACD,CAAA;GAED,IAAI,YAAY,KAAK,UACnB,KAAK,QACH,KAAK,UACL,QAAQ,GACR,IACA,CAAC,GAAG,oBAAoB,CAAC,MAAM,GAC/B,WACA,KACA,EAAE;EAGR,CAAC;CACH;CAEQ,YACN,MACA,MACA,UAAiB;EAEjB,IAAI,KAAK,MAAM,OAAO,KAAK;EAC3B,IAAI,SAAS,UAAU,OAAO,WAAW,mBAAmB;EAC5D,IAAI,SAAS,QACX,OAAO,KAAK,OAAO,gBAAgB,KAAK,IAAI,IAAI;EAGlD,OAAO;CACT;CAEQ,SACN,OACA,YACA,UAAgB;EAEhB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,KAAK,KAAK,MAAM,GAAG,WAAU,GAAI,KAAK;GAC5C,IAAI,OAAO,UAAU,OAAO;GAC5B,IAAI,KAAK,UAAU;IACjB,MAAM,QAAQ,KAAK,SAAS,KAAK,UAAU,IAAI,QAAQ;IACvD,IAAI,OAAO,OAAO;GACpB;EACF;EACA,OAAO;CACT;CA1RW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAiB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;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,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,kBAAA;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,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,YAAA;GAAA,WAAA;EAAA;EAAA,MAAA,EAAA,gBAAA,eAAA;EAAA,UAAA;EAAA,UC1K9B;EAkGA,QAAA,CAAA,29GAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDgEY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eAER,CAAC,iBAAiB;GAAC;GAAoB;GAAY;GAAW;GAAgB;GAAU;EAAa,CAAC,CAAC;EAAC,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAM5G,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAiB,YAAA,CAAA;EAX7B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAc,YACZ;GAAI,SACP,CAAC,aAAa;GAAC,eAET,CAAC,iBAAiB;IAAC;IAAoB;IAAY;IAAW;IAAgB;IAAU;GAAa,CAAC,CAAC;GAAC,iBAGtG,wBAAwB;GAAM,MACzC,EAAE,OAAO,eAAc;GAAE,UAAA;GAAA,QAAA,CAAA,29GAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-tree-view.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/tree-view/file-icons.ts","../../../../libs/angular-ui-kit/src/lib/tree-view/tree-view.component.ts","../../../../libs/angular-ui-kit/src/lib/tree-view/tree-view.component.html"],"sourcesContent":["/**\n * File-type → icon mapping for the TreeView component.\n *\n * Icon names refer to Tabler Icons (the design system's icon source).\n * Resolution order for a file node:\n * 1. explicit `node.icon`\n * 2. exact file name match (e.g. `package.json`, `Dockerfile`)\n * 3. file extension match (e.g. `.ts`, `.png`)\n * 4. generic file fallback\n */\n\n/** Icon used for collapsed folders. */\nexport const FOLDER_ICON = 'folder';\n/** Icon used for expanded (open) folders. */\nexport const FOLDER_OPEN_ICON = 'folder-open';\n/** Generic fallback icon for files with no specific match. */\nexport const FILE_FALLBACK_ICON = 'file';\n\n/** Map of well-known file names (lower-cased) to icons. */\nconst FILE_NAME_ICONS: Record<string, string> = {\n 'package.json': 'brand-npm',\n 'package-lock.json': 'brand-npm',\n 'yarn.lock': 'brand-yarn',\n 'pnpm-lock.yaml': 'brand-pnpm',\n 'dockerfile': 'brand-docker',\n 'docker-compose.yml': 'brand-docker',\n 'docker-compose.yaml': 'brand-docker',\n '.gitignore': 'brand-git',\n '.gitattributes': 'brand-git',\n '.gitmodules': 'brand-git',\n 'readme.md': 'book',\n 'license': 'license',\n 'license.md': 'license',\n '.env': 'key',\n '.env.local': 'key',\n '.editorconfig': 'settings',\n '.prettierrc': 'settings',\n '.eslintrc': 'settings',\n 'tsconfig.json': 'brand-typescript',\n 'angular.json': 'brand-angular',\n 'nx.json': 'settings',\n 'makefile': 'terminal',\n};\n\n/** Map of file extensions (without dot, lower-cased) to icons. */\nconst FILE_EXT_ICONS: Record<string, string> = {\n // Languages\n ts: 'brand-typescript',\n tsx: 'brand-typescript',\n js: 'brand-javascript',\n jsx: 'brand-javascript',\n mjs: 'brand-javascript',\n cjs: 'brand-javascript',\n html: 'brand-html5',\n htm: 'brand-html5',\n css: 'brand-css3',\n scss: 'brand-sass',\n sass: 'brand-sass',\n less: 'brand-css3',\n py: 'brand-python',\n rb: 'diamond',\n go: 'brand-golang',\n rs: 'brand-rust',\n java: 'coffee',\n kt: 'brand-kotlin',\n swift: 'brand-swift',\n c: 'file-code',\n h: 'file-code',\n cpp: 'brand-cpp',\n cs: 'brand-c-sharp',\n php: 'brand-php',\n dart: 'brand-flutter',\n sh: 'terminal-2',\n bash: 'terminal-2',\n zsh: 'terminal-2',\n sql: 'database',\n // Data / config\n json: 'braces',\n jsonc: 'braces',\n yml: 'file-code',\n yaml: 'file-code',\n toml: 'file-code',\n xml: 'file-code',\n ini: 'settings',\n env: 'key',\n // Docs\n md: 'markdown',\n mdx: 'markdown',\n txt: 'file-text',\n pdf: 'file-type-pdf',\n doc: 'file-text',\n docx: 'file-text',\n csv: 'file-spreadsheet',\n xls: 'file-spreadsheet',\n xlsx: 'file-spreadsheet',\n // Images\n png: 'photo',\n jpg: 'photo',\n jpeg: 'photo',\n gif: 'photo',\n webp: 'photo',\n svg: 'file-vector',\n ico: 'photo',\n bmp: 'photo',\n // Media\n mp3: 'music',\n wav: 'music',\n mp4: 'video',\n mov: 'video',\n webm: 'video',\n // Archives\n zip: 'file-zip',\n tar: 'file-zip',\n gz: 'file-zip',\n rar: 'file-zip',\n '7z': 'file-zip',\n // Fonts\n woff: 'typography',\n woff2: 'typography',\n ttf: 'typography',\n otf: 'typography',\n // Misc\n lock: 'lock',\n};\n\n/**\n * Resolve the icon name for a file based on its name.\n * Performs exact-name then extension lookup, falling back to a generic file icon.\n */\nexport function resolveFileIcon(fileName: string): string {\n const lower = fileName.toLowerCase();\n\n const byName = FILE_NAME_ICONS[lower];\n if (byName) return byName;\n\n const dotIndex = lower.lastIndexOf('.');\n if (dotIndex > 0 && dotIndex < lower.length - 1) {\n const ext = lower.slice(dotIndex + 1);\n const byExt = FILE_EXT_ICONS[ext];\n if (byExt) return byExt;\n }\n\n return FILE_FALLBACK_ICON;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK,\n ICON_CHEVRON_RIGHT,\n ICON_FILE,\n ICON_FILE_TEXT,\n ICON_KEY,\n ICON_LOCK,\n ICON_SETTINGS,\n} from '@lifecorner/angular-ui-kit/icons';\nimport {\n FILE_FALLBACK_ICON,\n FOLDER_ICON,\n FOLDER_OPEN_ICON,\n resolveFileIcon,\n} from './file-icons';\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\n/**\n * Node type. `'file'` and `'folder'` get the built-in file-tree treatment\n * (extension icons, folder open/close icons). Any other string marks a custom\n * domain type: such nodes render a colored type dot (or an explicit `icon`)\n * instead of file icons.\n */\nexport type TreeNodeType = 'file' | 'folder' | (string & {});\n\nexport type TreeNodeStatus =\n | 'default'\n | 'added'\n | 'modified'\n | 'removed'\n | 'muted'\n | 'success'\n | 'busy';\n\n/**\n * A node in the tree. Any node carrying `children` can expand — this covers\n * file trees (folders) as well as arbitrary domain hierarchies.\n */\nexport interface TreeNode {\n /** Display name, e.g. `app.component.ts` or `src`. */\n name: string;\n /**\n * Node type. Optional — inferred as `folder` when `children` is present,\n * otherwise `file`. May be any string for domain hierarchies.\n */\n type?: TreeNodeType;\n /**\n * Stable identifier. Optional — when omitted, the full path is used,\n * which is stable as long as names are unique among siblings.\n */\n id?: string;\n /** Child nodes. Any node with children can expand / collapse. */\n children?: TreeNode[];\n /**\n * Explicit Tabler icon name. Overrides automatic file-type / folder\n * icon resolution and the custom-type dot.\n */\n icon?: string;\n /**\n * Accent color for the node's icon / type dot. Any CSS color, including\n * `var(--…)` token references.\n */\n color?: string;\n /** Optional badge text shown to the right of the node (e.g. git status, count). */\n badge?: string;\n /**\n * Tone of the badge / node accent. `success` additionally renders a ✓\n * indicator, `busy` a pulsing dot (static under `prefers-reduced-motion`).\n */\n status?: TreeNodeStatus;\n /** Whether this node starts expanded. Ignored for nodes without children. */\n expanded?: boolean;\n /** Disable selection / interaction for this node. */\n disabled?: boolean;\n}\n\n// ── Internal flattened representation ─────────────────────────────────────────\n\ninterface FlatNode {\n id: string;\n name: string;\n type: TreeNodeType;\n depth: number;\n hasChildren: boolean;\n expanded: boolean;\n /** Resolved icon, or `null` for custom-type nodes that render a type dot. */\n icon: string | null;\n color?: string;\n badge?: string;\n status: TreeNodeStatus;\n disabled: boolean;\n /** Whether each ancestor level still has following siblings (for guide lines). */\n ancestorHasSibling: boolean[];\n /** Whether this node is the last among its siblings. */\n isLast: boolean;\n /** 1-based position among its siblings (aria-posinset). */\n posInSet: number;\n /** Number of siblings including itself (aria-setsize). */\n setSize: number;\n /** Id of the parent node, `null` for root nodes. */\n parentId: string | null;\n}\n\nconst BADGE_STATUS_COLOR: Record<TreeNodeStatus, string> = {\n default: 'var(--color-text-tertiary)',\n added: 'var(--color-success-default, #16a34a)',\n modified: 'var(--color-warning-default, #d97706)',\n removed: 'var(--color-error-default, #dc2626)',\n muted: 'var(--color-text-disabled)',\n success: 'var(--color-success-default, #16a34a)',\n busy: 'var(--color-primary-500, #6366f1)',\n};\n\nfunction nodeType(node: TreeNode): TreeNodeType {\n return node.type ?? (node.children ? 'folder' : 'file');\n}\n\nfunction isExpandable(node: TreeNode): boolean {\n return !!node.children?.length;\n}\n\n/**\n * Tree view component for visualizing hierarchies — file / folder trees as\n * well as arbitrary domain object trees with custom node types.\n *\n * Features:\n * - Recursive rendering from a single `nodes` input; any node with children\n * can expand / collapse\n * - Automatic file-type icons by extension and well-known file name,\n * with open / closed folder icons (for `file` / `folder` nodes)\n * - Custom node types: colored type dots or explicit icons via `type`,\n * `color`, `icon`\n * - Expand / collapse, with expand-all / collapse-all helpers\n * - Two-way bound selection and a `nodeClick` event\n * - Indentation guide lines for readability\n * - Optional per-node status badges (added / modified / removed) and status\n * indicators (`success` ✓, `busy` pulse)\n * - Keyboard accessible: roving tabindex (one tab stop), Arrow keys / Home /\n * End move focus, Enter / Space toggle or select, Left / Right collapse /\n * expand or move to parent / first child\n * - Dark / light theme support via design tokens\n *\n * @example\n * ```html\n * <lc-tree-view [nodes]=\"projectTree\" [(selectedId)]=\"selected\" />\n * ```\n */\n@Component({\n selector: 'lc-tree-view',\n standalone: true,\n imports: [IconComponent],\n // Its own glyphs and the inline ones of the built-in file-type map (file-icons.ts).\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT, ICON_CHECK, ICON_FILE, ICON_FILE_TEXT, ICON_KEY, ICON_LOCK, ICON_SETTINGS])],\n templateUrl: './tree-view.component.html',\n styleUrls: ['./tree-view.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'lc-tree-view' },\n})\nexport class TreeViewComponent {\n /** The root-level nodes of the tree. */\n readonly nodes = input.required<TreeNode[]>();\n\n /** Id (or path) of the currently selected node (two-way bindable). */\n readonly selectedId = model<string | null>(null);\n\n /** Show indentation guide lines. */\n readonly showGuides = input(true);\n\n /** Show the expand-all / collapse-all toolbar. */\n readonly showToolbar = input(true);\n\n /** Label of the toolbar's expand-all button. */\n readonly expandAllLabel = input('Expand all');\n\n /** Label of the toolbar's collapse-all button. */\n readonly collapseAllLabel = input('Collapse all');\n\n /** Icon size for node icons. */\n readonly iconSize = input<'xs' | 'sm' | 'md'>('sm');\n\n /** Accessible name of a folder's expand toggle. */\n readonly expandAriaLabel = input('Expand');\n\n /** Accessible name of a folder's collapse toggle. */\n readonly collapseAriaLabel = input('Collapse');\n\n /** Emitted when a node is clicked / activated. */\n readonly nodeClick = output<TreeNode>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Set of node ids the user has explicitly expanded / collapsed. */\n private readonly expandOverrides = signal<Map<string, boolean>>(new Map());\n\n /** Id of the node that last had keyboard focus (roving tabindex). */\n private readonly focusedId = signal<string | null>(null);\n\n /** Flattened, render-ready list of visible nodes. */\n protected readonly visibleNodes = computed<FlatNode[]>(() => {\n const out: FlatNode[] = [];\n const overrides = this.expandOverrides();\n this.flatten(this.nodes(), 0, '', [], overrides, out, null);\n return out;\n });\n\n /**\n * The single tab stop of the tree: the last focused node if it is still\n * visible, else the selected node, else the first enabled node.\n */\n protected readonly tabStopId = computed<string | null>(() => {\n const nodes = this.visibleNodes();\n const focusable = (id: string | null) =>\n id !== null && nodes.some((n) => n.id === id && !n.disabled);\n const focused = this.focusedId();\n if (focusable(focused)) return focused;\n const selected = this.selectedId();\n if (focusable(selected)) return selected;\n return nodes.find((n) => !n.disabled)?.id ?? null;\n });\n\n protected badgeColor(status: TreeNodeStatus): string {\n return BADGE_STATUS_COLOR[status];\n }\n\n protected onNodeClick(flat: FlatNode): void {\n if (flat.disabled) return;\n if (flat.hasChildren) {\n this.toggle(flat);\n }\n this.selectedId.set(flat.id);\n const original = this.findNode(this.nodes(), '', flat.id);\n if (original) this.nodeClick.emit(original);\n }\n\n protected onToggleClick(flat: FlatNode, event: Event): void {\n event.stopPropagation();\n if (!flat.disabled) this.toggle(flat);\n }\n\n protected onFocus(flat: FlatNode): void {\n this.focusedId.set(flat.id);\n }\n\n protected onKeydown(flat: FlatNode, event: KeyboardEvent): void {\n if (flat.disabled) return;\n const nodes = this.visibleNodes();\n const index = nodes.findIndex((n) => n.id === flat.id);\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n event.preventDefault();\n this.onNodeClick(flat);\n break;\n case 'ArrowRight':\n event.preventDefault();\n if (!flat.hasChildren) return;\n if (!flat.expanded) {\n this.setExpanded(flat.id, true);\n } else {\n // Expanded: move to the first child.\n this.focusIndex(index + 1);\n }\n break;\n case 'ArrowLeft':\n event.preventDefault();\n if (flat.hasChildren && flat.expanded) {\n this.setExpanded(flat.id, false);\n } else if (flat.parentId !== null) {\n // Leaf or collapsed node: move to the parent.\n this.focusIndex(nodes.findIndex((n) => n.id === flat.parentId));\n }\n break;\n case 'ArrowDown':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, index, 1));\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, index, -1));\n break;\n case 'Home':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, -1, 1));\n break;\n case 'End':\n event.preventDefault();\n this.focusIndex(this.nextEnabled(nodes, nodes.length, -1));\n break;\n }\n }\n\n /** Expand every folder in the tree. */\n expandAll(): void {\n this.setAll(true);\n }\n\n /** Collapse every folder in the tree. */\n collapseAll(): void {\n this.setAll(false);\n }\n\n protected trackById = (_: number, n: FlatNode): string => n.id;\n\n // ── internals ──────────────────────────────────────────────────────────────\n\n /** Index of the next enabled node from `from` in direction `dir`, or -1. */\n private nextEnabled(nodes: FlatNode[], from: number, dir: 1 | -1): number {\n for (let i = from + dir; i >= 0 && i < nodes.length; i += dir) {\n if (!nodes[i].disabled) return i;\n }\n return -1;\n }\n\n /**\n * Moves keyboard focus to the visible node at `index`. The rows are\n * rendered in `visibleNodes()` order, so the index maps 1:1 to the DOM.\n */\n private focusIndex(index: number): void {\n const nodes = this.visibleNodes();\n if (index < 0 || index >= nodes.length || nodes[index].disabled) return;\n this.focusedId.set(nodes[index].id);\n const items = this.host.nativeElement.querySelectorAll<HTMLElement>('.lc-tree-view__item');\n items[index]?.focus();\n }\n\n private toggle(flat: FlatNode): void {\n this.setExpanded(flat.id, !flat.expanded);\n }\n\n private setExpanded(id: string, value: boolean): void {\n this.expandOverrides.update((map) => {\n const next = new Map(map);\n next.set(id, value);\n return next;\n });\n }\n\n private setAll(value: boolean): void {\n const next = new Map<string, boolean>();\n const walk = (nodes: TreeNode[], parentPath: string): void => {\n for (const node of nodes) {\n const id = node.id ?? `${parentPath}/${node.name}`;\n if (node.children?.length) {\n next.set(id, value);\n walk(node.children, id);\n }\n }\n };\n walk(this.nodes(), '');\n this.expandOverrides.set(next);\n }\n\n private isExpanded(\n id: string,\n node: TreeNode,\n overrides: Map<string, boolean>,\n ): boolean {\n const override = overrides.get(id);\n if (override !== undefined) return override;\n return node.expanded ?? false;\n }\n\n private flatten(\n nodes: TreeNode[],\n depth: number,\n parentPath: string,\n ancestorHasSibling: boolean[],\n overrides: Map<string, boolean>,\n out: FlatNode[],\n parentId: string | null,\n ): void {\n nodes.forEach((node, index) => {\n const id = node.id ?? `${parentPath}/${node.name}`;\n const type = nodeType(node);\n const hasChildren = isExpandable(node);\n const expanded = hasChildren && this.isExpanded(id, node, overrides);\n const isLast = index === nodes.length - 1;\n\n out.push({\n id,\n name: node.name,\n type,\n depth,\n hasChildren,\n expanded,\n icon: this.resolveIcon(node, type, expanded),\n color: node.color,\n badge: node.badge,\n status: node.status ?? 'default',\n disabled: node.disabled ?? false,\n ancestorHasSibling,\n isLast,\n posInSet: index + 1,\n setSize: nodes.length,\n parentId,\n });\n\n if (expanded && node.children) {\n this.flatten(\n node.children,\n depth + 1,\n id,\n [...ancestorHasSibling, !isLast],\n overrides,\n out,\n id,\n );\n }\n });\n }\n\n private resolveIcon(\n node: TreeNode,\n type: TreeNodeType,\n expanded: boolean,\n ): string | null {\n if (node.icon) return node.icon;\n if (type === 'folder') return expanded ? FOLDER_OPEN_ICON : FOLDER_ICON;\n if (type === 'file') {\n return node.name ? resolveFileIcon(node.name) : FILE_FALLBACK_ICON;\n }\n // Custom domain type without explicit icon: render a colored type dot.\n return null;\n }\n\n private findNode(\n nodes: TreeNode[],\n parentPath: string,\n targetId: string,\n ): TreeNode | null {\n for (const node of nodes) {\n const id = node.id ?? `${parentPath}/${node.name}`;\n if (id === targetId) return node;\n if (node.children) {\n const found = this.findNode(node.children, id, targetId);\n if (found) return found;\n }\n }\n return null;\n }\n}\n","@if (showToolbar()) {\n <div class=\"lc-tree-view__toolbar\">\n <button type=\"button\" class=\"lc-tree-view__tool-btn\" (click)=\"expandAll()\" [attr.title]=\"expandAllLabel()\">\n <lc-icon name=\"fold-down\" size=\"xs\" [decorative]=\"true\" />\n <span>{{ expandAllLabel() }}</span>\n </button>\n <button type=\"button\" class=\"lc-tree-view__tool-btn\" (click)=\"collapseAll()\" [attr.title]=\"collapseAllLabel()\">\n <lc-icon name=\"fold-up\" size=\"xs\" [decorative]=\"true\" />\n <span>{{ collapseAllLabel() }}</span>\n </button>\n </div>\n}\n\n<ul class=\"lc-tree-view__list\" role=\"tree\">\n @for (node of visibleNodes(); track trackById($index, node)) {\n <li\n class=\"lc-tree-view__item\"\n [class.lc-tree-view__item--selected]=\"node.id === selectedId()\"\n [class.lc-tree-view__item--disabled]=\"node.disabled\"\n [class.lc-tree-view__item--folder]=\"node.type === 'folder'\"\n role=\"treeitem\"\n [attr.aria-level]=\"node.depth + 1\"\n [attr.aria-posinset]=\"node.posInSet\"\n [attr.aria-setsize]=\"node.setSize\"\n [attr.aria-expanded]=\"node.hasChildren ? node.expanded : null\"\n [attr.aria-selected]=\"node.id === selectedId()\"\n [attr.aria-disabled]=\"node.disabled ? true : null\"\n [attr.aria-busy]=\"node.status === 'busy' ? true : null\"\n [tabindex]=\"node.id === tabStopId() ? 0 : -1\"\n (focus)=\"onFocus(node)\"\n (click)=\"onNodeClick(node)\"\n (keydown)=\"onKeydown(node, $event)\"\n >\n <!-- indentation + guide lines -->\n @for (hasSibling of node.ancestorHasSibling; track $index) {\n <span\n class=\"lc-tree-view__indent\"\n [class.lc-tree-view__indent--guide]=\"showGuides() && hasSibling\"\n ></span>\n }\n\n <!-- expand / collapse chevron -->\n <span class=\"lc-tree-view__chevron\">\n @if (node.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-tree-view__chevron-btn\"\n [class.lc-tree-view__chevron-btn--expanded]=\"node.expanded\"\n (click)=\"onToggleClick(node, $event)\"\n [attr.aria-label]=\"node.expanded ? collapseAriaLabel() : expandAriaLabel()\"\n tabindex=\"-1\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n\n <!-- type / file icon, or type dot for custom node types -->\n <span\n class=\"lc-tree-view__icon\"\n [style.color]=\"\n node.color ?? (node.status === 'default' ? null : badgeColor(node.status))\n \"\n >\n @if (node.icon; as icon) {\n <lc-icon [name]=\"icon\" [size]=\"iconSize()\" [decorative]=\"true\" />\n } @else {\n <span\n class=\"lc-tree-view__type-dot\"\n [style.background-color]=\"node.color ?? null\"\n ></span>\n }\n </span>\n\n <!-- label -->\n <span class=\"lc-tree-view__label\">{{ node.name }}</span>\n\n <!-- badge -->\n @if (node.badge) {\n <span\n class=\"lc-tree-view__badge\"\n [style.color]=\"badgeColor(node.status)\"\n [style.border-color]=\"badgeColor(node.status)\"\n >{{ node.badge }}</span\n >\n }\n\n <!-- status indicator -->\n @if (node.status === 'success') {\n <span class=\"lc-tree-view__status lc-tree-view__status--success\">\n <lc-icon name=\"check\" size=\"xs\" [decorative]=\"true\" />\n </span>\n } @else if (node.status === 'busy') {\n <span class=\"lc-tree-view__status lc-tree-view__status--busy\"></span>\n }\n </li>\n }\n</ul>\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifecorner/angular-ui-kit",
3
- "version": "3.21.1",
3
+ "version": "3.22.0",
4
4
  "description": "LifeCorner Design System – Angular UI component library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -136,8 +136,21 @@ export declare const ICON_WALLET: LcIconDefinition;
136
136
  export declare const ICON_BUILDING_BANK: LcIconDefinition;
137
137
  export declare const ICON_SHIELD_LOCK: LcIconDefinition;
138
138
  export declare const ICON_SHIELD_LOCK_SOLID: LcIconDefinition;
139
+ export declare const ICON_SEARCH: LcIconDefinition;
140
+ export declare const ICON_SEARCH_SOLID: LcIconDefinition;
141
+ export declare const ICON_LOCK: LcIconDefinition;
142
+ export declare const ICON_LOCK_SOLID: LcIconDefinition;
143
+ export declare const ICON_DEVICE_TABLET: LcIconDefinition;
144
+ export declare const ICON_DEVICE_TABLET_SOLID: LcIconDefinition;
145
+ export declare const ICON_PLAYER_PLAY: LcIconDefinition;
146
+ export declare const ICON_PLAYER_PLAY_SOLID: LcIconDefinition;
147
+ export declare const ICON_PLAYER_PAUSE: LcIconDefinition;
148
+ export declare const ICON_PLAYER_PAUSE_SOLID: LcIconDefinition;
149
+ export declare const ICON_CLIPBOARD: LcIconDefinition;
150
+ export declare const ICON_CLIPBOARD_SOLID: LcIconDefinition;
151
+ export declare const ICON_ARROW_DOWN: LcIconDefinition;
139
152
  /**
140
- * Every glyph above (137): `provideLcIcons(BUNDLED_ICONS)` restores the
153
+ * Every glyph above (150): `provideLcIcons(BUNDLED_ICONS)` restores the
141
154
  * synchronous rendering of 3.5.1 for every inline name, e.g. in tests or
142
155
  * Storybook. An app that imports it bundles all of them.
143
156
  */