@life-cockpit/angular-ui-kit 1.2.2 → 1.3.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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, PLATFORM_ID, signal, Injectable, input, computed, effect, ChangeDetectionStrategy, Component, model, EventEmitter, ViewChild, Output, Input, ViewEncapsulation, output, HostListener, forwardRef, ElementRef, viewChild, ChangeDetectorRef, ViewChildren, HostBinding, ViewContainerRef, Renderer2, Directive, TemplateRef, ContentChildren, ContentChild } from '@angular/core';
2
+ import { inject, PLATFORM_ID, signal, Injectable, input, computed, effect, ChangeDetectionStrategy, Component, model, EventEmitter, ViewChild, Output, Input, ViewEncapsulation, output, HostListener, forwardRef, ElementRef, viewChild, ChangeDetectorRef, ViewChildren, HostBinding, ViewContainerRef, Renderer2, Directive, TemplateRef, ContentChildren, ContentChild, SecurityContext, NgZone, ApplicationRef, EnvironmentInjector, createComponent } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { isPlatformBrowser, CommonModule, SlicePipe, NgStyle } from '@angular/common';
5
5
  import * as i1$4 from '@angular/platform-browser';
@@ -9,7 +9,7 @@ import * as i1$2 from '@angular/forms';
9
9
  import { NG_VALUE_ACCESSOR, NgControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
10
10
  import * as i1$1 from '@angular/cdk/overlay';
11
11
  import { OverlayModule, Overlay } from '@angular/cdk/overlay';
12
- import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs';
12
+ import { Subject, debounceTime, distinctUntilChanged, takeUntil, switchMap, of } from 'rxjs';
13
13
  import * as i1$3 from '@angular/cdk/a11y';
14
14
  import { ConfigurableFocusTrapFactory, A11yModule } from '@angular/cdk/a11y';
15
15
  import { ComponentPortal } from '@angular/cdk/portal';
@@ -12848,6 +12848,1089 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
12848
12848
  args: ['richArea']
12849
12849
  }] } });
12850
12850
 
12851
+ /**
12852
+ * Markdown renderer component.
12853
+ *
12854
+ * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with
12855
+ * optional syntax highlighting via `<lc-code-block>`.
12856
+ *
12857
+ * @example
12858
+ * ```html
12859
+ * <lc-markdown [content]="'# Hello World'" />
12860
+ * <lc-markdown [src]="'/docs/readme.md'" />
12861
+ * ```
12862
+ */
12863
+ class MarkdownComponent {
12864
+ sanitizer = inject(DomSanitizer);
12865
+ http = inject(HttpClient);
12866
+ httpSub;
12867
+ /** URL or path to load markdown from */
12868
+ src = input(...(ngDevMode ? [undefined, { debugName: "src" }] : /* istanbul ignore next */ []));
12869
+ /** Raw markdown string */
12870
+ content = input(...(ngDevMode ? [undefined, { debugName: "content" }] : /* istanbul ignore next */ []));
12871
+ /** Display variant */
12872
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
12873
+ /** Target for links */
12874
+ linkTarget = input('_self', ...(ngDevMode ? [{ debugName: "linkTarget" }] : /* istanbul ignore next */ []));
12875
+ /** Whether to sanitize HTML output */
12876
+ sanitize = input(true, ...(ngDevMode ? [{ debugName: "sanitize" }] : /* istanbul ignore next */ []));
12877
+ /** Whether to use code-block for fenced code */
12878
+ syntaxHighlight = input(true, ...(ngDevMode ? [{ debugName: "syntaxHighlight" }] : /* istanbul ignore next */ []));
12879
+ /** Whether to show anchor links on headings */
12880
+ showHeadingAnchors = input(false, ...(ngDevMode ? [{ debugName: "showHeadingAnchors" }] : /* istanbul ignore next */ []));
12881
+ /** Base URL for resolving relative links/images */
12882
+ baseUrl = input(...(ngDevMode ? [undefined, { debugName: "baseUrl" }] : /* istanbul ignore next */ []));
12883
+ /** Emitted when a link is clicked */
12884
+ linkClick = output();
12885
+ /** Emitted after rendering with heading TOC */
12886
+ rendered = output();
12887
+ /** Internal resolved markdown source */
12888
+ resolvedMarkdown = signal('', ...(ngDevMode ? [{ debugName: "resolvedMarkdown" }] : /* istanbul ignore next */ []));
12889
+ /** Parsed result (computed once from resolvedMarkdown) */
12890
+ parsed = computed(() => {
12891
+ const md = this.resolvedMarkdown();
12892
+ if (!md)
12893
+ return { html: '', blocks: [], headings: [] };
12894
+ return this.parseMarkdown(md);
12895
+ }, ...(ngDevMode ? [{ debugName: "parsed" }] : /* istanbul ignore next */ []));
12896
+ /** Computed render parts (HTML chunks + code blocks interleaved) */
12897
+ renderParts = computed(() => {
12898
+ const { html, blocks } = this.parsed();
12899
+ if (!html)
12900
+ return [];
12901
+ if (this.syntaxHighlight() && blocks.length > 0) {
12902
+ return this.splitIntoParts(html, blocks);
12903
+ }
12904
+ // No code blocks — single HTML part
12905
+ const sanitized = this.sanitize()
12906
+ ? this.sanitizer.sanitize(SecurityContext.HTML, html) || ''
12907
+ : html;
12908
+ return [{
12909
+ type: 'html',
12910
+ index: 0,
12911
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
12912
+ }];
12913
+ }, ...(ngDevMode ? [{ debugName: "renderParts" }] : /* istanbul ignore next */ []));
12914
+ containerClasses = computed(() => {
12915
+ return `lc-markdown lc-markdown--${this.variant()}`;
12916
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
12917
+ headings = computed(() => this.parsed().headings, ...(ngDevMode ? [{ debugName: "headings" }] : /* istanbul ignore next */ []));
12918
+ constructor() {
12919
+ // Load content from src when src changes
12920
+ effect(() => {
12921
+ const src = this.src();
12922
+ if (src) {
12923
+ this.loadFromUrl(src);
12924
+ }
12925
+ });
12926
+ // Use raw content when provided
12927
+ effect(() => {
12928
+ const content = this.content();
12929
+ if (content !== undefined) {
12930
+ this.resolvedMarkdown.set(content);
12931
+ }
12932
+ });
12933
+ // Emit rendered event when headings change
12934
+ effect(() => {
12935
+ const h = this.headings();
12936
+ if (h.length > 0) {
12937
+ this.rendered.emit({ headings: h });
12938
+ }
12939
+ });
12940
+ }
12941
+ ngOnDestroy() {
12942
+ this.httpSub?.unsubscribe();
12943
+ }
12944
+ onLinkClick(event) {
12945
+ const target = event.target;
12946
+ const anchor = target.closest('a');
12947
+ if (anchor) {
12948
+ this.linkClick.emit({ href: anchor.href, event });
12949
+ }
12950
+ }
12951
+ loadFromUrl(url) {
12952
+ this.httpSub?.unsubscribe();
12953
+ this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({
12954
+ next: (text) => this.resolvedMarkdown.set(text),
12955
+ error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),
12956
+ });
12957
+ }
12958
+ splitIntoParts(html, blocks) {
12959
+ const parts = [];
12960
+ // The placeholders survive HTML escaping as &lt;!--CODE_BLOCK_N--&gt;
12961
+ const regex = /&lt;!--CODE_BLOCK_(\d+)--&gt;/g;
12962
+ let lastIndex = 0;
12963
+ let partIndex = 0;
12964
+ let match;
12965
+ while ((match = regex.exec(html)) !== null) {
12966
+ const before = html.slice(lastIndex, match.index);
12967
+ if (before.trim()) {
12968
+ const sanitized = this.sanitize()
12969
+ ? this.sanitizer.sanitize(SecurityContext.HTML, before) || ''
12970
+ : before;
12971
+ parts.push({
12972
+ type: 'html',
12973
+ index: partIndex++,
12974
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
12975
+ });
12976
+ }
12977
+ const blockIdx = parseInt(match[1], 10);
12978
+ parts.push({
12979
+ type: 'code',
12980
+ index: partIndex++,
12981
+ code: blocks[blockIdx].code,
12982
+ lang: blocks[blockIdx].lang,
12983
+ });
12984
+ lastIndex = match.index + match[0].length;
12985
+ }
12986
+ const remaining = html.slice(lastIndex);
12987
+ if (remaining.trim()) {
12988
+ const sanitized = this.sanitize()
12989
+ ? this.sanitizer.sanitize(SecurityContext.HTML, remaining) || ''
12990
+ : remaining;
12991
+ parts.push({
12992
+ type: 'html',
12993
+ index: partIndex++,
12994
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
12995
+ });
12996
+ }
12997
+ return parts;
12998
+ }
12999
+ parseMarkdown(md) {
13000
+ const blocks = [];
13001
+ const headings = [];
13002
+ const baseUrl = this.baseUrl();
13003
+ const linkTarget = this.linkTarget();
13004
+ const showAnchors = this.showHeadingAnchors();
13005
+ // 1. Extract fenced code blocks → placeholders
13006
+ let processed = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
13007
+ const idx = blocks.length;
13008
+ blocks.push({ lang: lang || 'text', code: code.trimEnd() });
13009
+ return `<!--CODE_BLOCK_${idx}-->`;
13010
+ });
13011
+ // 2. Escape HTML entities
13012
+ processed = processed
13013
+ .replace(/&/g, '&amp;')
13014
+ .replace(/</g, '&lt;')
13015
+ .replace(/>/g, '&gt;');
13016
+ // 3. Headings (# to ######)
13017
+ processed = processed.replace(/^(#{1,6})\s+(.+)$/gm, (_match, hashes, text) => {
13018
+ const level = hashes.length;
13019
+ const id = text
13020
+ .toLowerCase()
13021
+ .replace(/[^\w\s-]/g, '')
13022
+ .replace(/\s+/g, '-');
13023
+ headings.push({ level, text, id });
13024
+ const anchor = showAnchors
13025
+ ? `<a href="#${id}" class="lc-markdown__anchor" aria-hidden="true">#</a>`
13026
+ : '';
13027
+ return `<h${level} id="${id}">${anchor}${text}</h${level}>`;
13028
+ });
13029
+ // 4. Horizontal rules
13030
+ processed = processed.replace(/^---+$/gm, '<hr>');
13031
+ // 5. Blockquotes
13032
+ processed = processed.replace(/^(?:&gt;)\s?(.*)$/gm, '<blockquote>$1</blockquote>');
13033
+ // Merge consecutive blockquotes
13034
+ processed = processed.replace(/<\/blockquote>\n<blockquote>/g, '\n');
13035
+ // 6. Tables (GFM)
13036
+ processed = this.parseTables(processed);
13037
+ // 7. Task lists
13038
+ processed = processed.replace(/^[-*]\s+\[x\]\s+(.*)$/gm, '<li class="lc-markdown__task"><input type="checkbox" checked disabled> $1</li>');
13039
+ processed = processed.replace(/^[-*]\s+\[ \]\s+(.*)$/gm, '<li class="lc-markdown__task"><input type="checkbox" disabled> $1</li>');
13040
+ // 8. Unordered lists
13041
+ processed = processed.replace(/^[-*]\s+(.*)$/gm, '<li>$1</li>');
13042
+ processed = processed.replace(/(<li>[\s\S]*?<\/li>)/g, (match) => {
13043
+ if (!match.includes('lc-markdown__task')) {
13044
+ return match;
13045
+ }
13046
+ return match;
13047
+ });
13048
+ // Wrap consecutive <li> in <ul>
13049
+ processed = processed.replace(/((?:<li[^>]*>.*<\/li>\n?)+)/g, '<ul>$1</ul>');
13050
+ // 9. Ordered lists
13051
+ processed = processed.replace(/^\d+\.\s+(.*)$/gm, '<li>$1</li>');
13052
+ // 10. Inline code
13053
+ processed = processed.replace(/`([^`]+)`/g, '<code>$1</code>');
13054
+ // 11. Bold + italic combos
13055
+ processed = processed.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
13056
+ processed = processed.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
13057
+ processed = processed.replace(/\*(.+?)\*/g, '<em>$1</em>');
13058
+ // 12. Strikethrough (GFM)
13059
+ processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');
13060
+ // 13. Images
13061
+ processed = processed.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, src) => {
13062
+ const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;
13063
+ return `<img src="${resolvedSrc}" alt="${alt}" class="lc-markdown__img">`;
13064
+ });
13065
+ // 14. Links
13066
+ processed = processed.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, href) => {
13067
+ const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')
13068
+ ? `${baseUrl}/${href}`
13069
+ : href;
13070
+ return `<a href="${resolvedHref}" target="${linkTarget}" rel="noopener">${text}</a>`;
13071
+ });
13072
+ // 15. Paragraphs — wrap standalone lines
13073
+ processed = processed
13074
+ .split('\n\n')
13075
+ .map((block) => {
13076
+ const trimmed = block.trim();
13077
+ if (!trimmed)
13078
+ return '';
13079
+ if (trimmed.startsWith('<h') ||
13080
+ trimmed.startsWith('<ul') ||
13081
+ trimmed.startsWith('<ol') ||
13082
+ trimmed.startsWith('<blockquote') ||
13083
+ trimmed.startsWith('<hr') ||
13084
+ trimmed.startsWith('<table') ||
13085
+ trimmed.startsWith('<!--CODE_BLOCK')) {
13086
+ return trimmed;
13087
+ }
13088
+ return `<p>${trimmed.replace(/\n/g, '<br>')}</p>`;
13089
+ })
13090
+ .join('\n');
13091
+ // 16. Restore code block placeholders
13092
+ if (this.syntaxHighlight()) {
13093
+ // Leave placeholders — the template will render <lc-code-block> for each
13094
+ // No replacement needed here
13095
+ }
13096
+ else {
13097
+ processed = processed.replace(/&lt;!--CODE_BLOCK_(\d+)--&gt;/g, (_match, idx) => {
13098
+ const block = blocks[parseInt(idx, 10)];
13099
+ return `<pre><code class="language-${block.lang}">${this.escapeHtml(block.code)}</code></pre>`;
13100
+ });
13101
+ }
13102
+ return { html: processed, blocks, headings };
13103
+ }
13104
+ parseTables(text) {
13105
+ return text.replace(/^(\|.+\|)\n(\|[-| :]+\|)\n((?:\|.+\|\n?)*)/gm, (_match, header, _separator, body) => {
13106
+ const headers = header
13107
+ .split('|')
13108
+ .filter((c) => c.trim())
13109
+ .map((c) => `<th>${c.trim()}</th>`)
13110
+ .join('');
13111
+ const rows = body
13112
+ .trim()
13113
+ .split('\n')
13114
+ .map((row) => {
13115
+ const cells = row
13116
+ .split('|')
13117
+ .filter((c) => c.trim())
13118
+ .map((c) => `<td>${c.trim()}</td>`)
13119
+ .join('');
13120
+ return `<tr>${cells}</tr>`;
13121
+ })
13122
+ .join('');
13123
+ return `<table class="lc-markdown__table"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>`;
13124
+ });
13125
+ }
13126
+ escapeHtml(text) {
13127
+ return text
13128
+ .replace(/&/g, '&amp;')
13129
+ .replace(/</g, '&lt;')
13130
+ .replace(/>/g, '&gt;');
13131
+ }
13132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: MarkdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: MarkdownComponent, isStandalone: true, selector: "lc-markdown", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, linkTarget: { classPropertyName: "linkTarget", publicName: "linkTarget", isSignal: true, isRequired: false, transformFunction: null }, sanitize: { classPropertyName: "sanitize", publicName: "sanitize", isSignal: true, isRequired: false, transformFunction: null }, syntaxHighlight: { classPropertyName: "syntaxHighlight", publicName: "syntaxHighlight", isSignal: true, isRequired: false, transformFunction: null }, showHeadingAnchors: { classPropertyName: "showHeadingAnchors", publicName: "showHeadingAnchors", isSignal: true, isRequired: false, transformFunction: null }, baseUrl: { classPropertyName: "baseUrl", publicName: "baseUrl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { linkClick: "linkClick", rendered: "rendered" }, ngImport: i0, template: "<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"$any(part.lang)\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n }\n }\n</div>\n", styles: [".lc-markdown{color:var(--color-text, #111827);font-family:var(--font-family-sans);font-size:var(--font-size-base, 1rem);line-height:var(--line-height-relaxed, 1.75)}.lc-markdown h1,.lc-markdown h2,.lc-markdown h3,.lc-markdown h4,.lc-markdown h5,.lc-markdown h6{color:var(--color-text, #111827);font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-tight, 1.25);margin:1.5em 0 .5em}.lc-markdown h1:first-child,.lc-markdown h2:first-child,.lc-markdown h3:first-child,.lc-markdown h4:first-child,.lc-markdown h5:first-child,.lc-markdown h6:first-child{margin-top:0}.lc-markdown h1{font-size:2rem}.lc-markdown h2{font-size:1.5rem}.lc-markdown h3{font-size:1.25rem}.lc-markdown h4{font-size:1.125rem}.lc-markdown h5{font-size:1rem}.lc-markdown h6{font-size:.875rem}.lc-markdown p{margin:0 0 1em}.lc-markdown a{color:var(--color-primary-600, #2563eb);text-decoration:none}.lc-markdown a:hover{text-decoration:underline}.lc-markdown strong{font-weight:var(--font-weight-semibold, 600)}.lc-markdown code{background-color:var(--color-neutral-100, #f3f4f6);color:var(--color-neutral-800, #1f2937);padding:.125em .375em;border-radius:var(--radius-sm, .25rem);font-family:var(--font-family-mono, monospace);font-size:.875em}.lc-markdown pre{margin:1em 0;overflow-x:auto}.lc-markdown pre code{background:none;padding:0;border-radius:0}.lc-markdown blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--color-primary-300, #93c5fd);background-color:var(--color-neutral-50, #f9fafb);color:var(--color-text-secondary, #4b5563)}.lc-markdown blockquote p{margin:0}.lc-markdown ul,.lc-markdown ol{margin:0 0 1em;padding-left:1.5em}.lc-markdown li{margin:.25em 0}.lc-markdown hr{border:none;border-top:1px solid var(--color-divider, #e5e7eb);margin:1.5em 0}.lc-markdown img{max-width:100%;height:auto;border-radius:var(--radius-md, .375rem)}.lc-markdown del{text-decoration:line-through;color:var(--color-text-secondary, #6b7280)}.lc-markdown__table{width:100%;border-collapse:collapse;margin:1em 0;font-size:var(--font-size-sm, .875rem)}.lc-markdown__table th,.lc-markdown__table td{padding:.5rem .75rem;border:1px solid var(--color-divider, #e5e7eb);text-align:left}.lc-markdown__table th{background-color:var(--color-neutral-50, #f9fafb);font-weight:var(--font-weight-semibold, 600)}.lc-markdown__table tr:hover td{background-color:var(--color-neutral-50, #f9fafb)}.lc-markdown__task{list-style:none;margin-left:-1.5em}.lc-markdown__task input[type=checkbox]{margin-right:.5em;vertical-align:middle}.lc-markdown__anchor{color:var(--color-neutral-400, #9ca3af);text-decoration:none;margin-right:.25em;opacity:0;transition:opacity .15s ease}h1:hover .lc-markdown__anchor,h2:hover .lc-markdown__anchor,h3:hover .lc-markdown__anchor,h4:hover .lc-markdown__anchor,h5:hover .lc-markdown__anchor,h6:hover .lc-markdown__anchor{opacity:1}.lc-markdown lc-code-block{display:block;margin:1em 0}.lc-markdown--compact{font-size:var(--font-size-sm, .875rem);line-height:var(--line-height-normal, 1.5)}.lc-markdown--compact h1{font-size:1.5rem}.lc-markdown--compact h2{font-size:1.25rem}.lc-markdown--compact h3{font-size:1.125rem}.lc-markdown--compact h4{font-size:1rem}.lc-markdown--compact h5,.lc-markdown--compact h6{font-size:.875rem}.lc-markdown--compact h1,.lc-markdown--compact h2,.lc-markdown--compact h3,.lc-markdown--compact h4,.lc-markdown--compact h5,.lc-markdown--compact h6{margin:1em 0 .25em}.lc-markdown--compact p{margin:0 0 .5em}.lc-markdown--compact blockquote{margin:.5em 0;padding:.25em .75em}.lc-markdown--compact ul,.lc-markdown--compact ol{margin:0 0 .5em}\n"], dependencies: [{ kind: "component", type: CodeBlockComponent, selector: "lc-code-block", inputs: ["code", "language", "filename", "showLineNumbers", "showCopy", "showHeader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13134
+ }
13135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: MarkdownComponent, decorators: [{
13136
+ type: Component,
13137
+ args: [{ selector: 'lc-markdown', standalone: true, imports: [CodeBlockComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"$any(part.lang)\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n }\n }\n</div>\n", styles: [".lc-markdown{color:var(--color-text, #111827);font-family:var(--font-family-sans);font-size:var(--font-size-base, 1rem);line-height:var(--line-height-relaxed, 1.75)}.lc-markdown h1,.lc-markdown h2,.lc-markdown h3,.lc-markdown h4,.lc-markdown h5,.lc-markdown h6{color:var(--color-text, #111827);font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-tight, 1.25);margin:1.5em 0 .5em}.lc-markdown h1:first-child,.lc-markdown h2:first-child,.lc-markdown h3:first-child,.lc-markdown h4:first-child,.lc-markdown h5:first-child,.lc-markdown h6:first-child{margin-top:0}.lc-markdown h1{font-size:2rem}.lc-markdown h2{font-size:1.5rem}.lc-markdown h3{font-size:1.25rem}.lc-markdown h4{font-size:1.125rem}.lc-markdown h5{font-size:1rem}.lc-markdown h6{font-size:.875rem}.lc-markdown p{margin:0 0 1em}.lc-markdown a{color:var(--color-primary-600, #2563eb);text-decoration:none}.lc-markdown a:hover{text-decoration:underline}.lc-markdown strong{font-weight:var(--font-weight-semibold, 600)}.lc-markdown code{background-color:var(--color-neutral-100, #f3f4f6);color:var(--color-neutral-800, #1f2937);padding:.125em .375em;border-radius:var(--radius-sm, .25rem);font-family:var(--font-family-mono, monospace);font-size:.875em}.lc-markdown pre{margin:1em 0;overflow-x:auto}.lc-markdown pre code{background:none;padding:0;border-radius:0}.lc-markdown blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--color-primary-300, #93c5fd);background-color:var(--color-neutral-50, #f9fafb);color:var(--color-text-secondary, #4b5563)}.lc-markdown blockquote p{margin:0}.lc-markdown ul,.lc-markdown ol{margin:0 0 1em;padding-left:1.5em}.lc-markdown li{margin:.25em 0}.lc-markdown hr{border:none;border-top:1px solid var(--color-divider, #e5e7eb);margin:1.5em 0}.lc-markdown img{max-width:100%;height:auto;border-radius:var(--radius-md, .375rem)}.lc-markdown del{text-decoration:line-through;color:var(--color-text-secondary, #6b7280)}.lc-markdown__table{width:100%;border-collapse:collapse;margin:1em 0;font-size:var(--font-size-sm, .875rem)}.lc-markdown__table th,.lc-markdown__table td{padding:.5rem .75rem;border:1px solid var(--color-divider, #e5e7eb);text-align:left}.lc-markdown__table th{background-color:var(--color-neutral-50, #f9fafb);font-weight:var(--font-weight-semibold, 600)}.lc-markdown__table tr:hover td{background-color:var(--color-neutral-50, #f9fafb)}.lc-markdown__task{list-style:none;margin-left:-1.5em}.lc-markdown__task input[type=checkbox]{margin-right:.5em;vertical-align:middle}.lc-markdown__anchor{color:var(--color-neutral-400, #9ca3af);text-decoration:none;margin-right:.25em;opacity:0;transition:opacity .15s ease}h1:hover .lc-markdown__anchor,h2:hover .lc-markdown__anchor,h3:hover .lc-markdown__anchor,h4:hover .lc-markdown__anchor,h5:hover .lc-markdown__anchor,h6:hover .lc-markdown__anchor{opacity:1}.lc-markdown lc-code-block{display:block;margin:1em 0}.lc-markdown--compact{font-size:var(--font-size-sm, .875rem);line-height:var(--line-height-normal, 1.5)}.lc-markdown--compact h1{font-size:1.5rem}.lc-markdown--compact h2{font-size:1.25rem}.lc-markdown--compact h3{font-size:1.125rem}.lc-markdown--compact h4{font-size:1rem}.lc-markdown--compact h5,.lc-markdown--compact h6{font-size:.875rem}.lc-markdown--compact h1,.lc-markdown--compact h2,.lc-markdown--compact h3,.lc-markdown--compact h4,.lc-markdown--compact h5,.lc-markdown--compact h6{margin:1em 0 .25em}.lc-markdown--compact p{margin:0 0 .5em}.lc-markdown--compact blockquote{margin:.5em 0;padding:.25em .75em}.lc-markdown--compact ul,.lc-markdown--compact ol{margin:0 0 .5em}\n"] }]
13138
+ }], ctorParameters: () => [], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], linkTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkTarget", required: false }] }], sanitize: [{ type: i0.Input, args: [{ isSignal: true, alias: "sanitize", required: false }] }], syntaxHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "syntaxHighlight", required: false }] }], showHeadingAnchors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeadingAnchors", required: false }] }], baseUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "baseUrl", required: false }] }], linkClick: [{ type: i0.Output, args: ["linkClick"] }], rendered: [{ type: i0.Output, args: ["rendered"] }] } });
13139
+
13140
+ /**
13141
+ * Streaming log / terminal viewer component.
13142
+ *
13143
+ * Supports controlled (lines input) and streaming (stream$ observable) modes,
13144
+ * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,
13145
+ * and filtering.
13146
+ *
13147
+ * @example
13148
+ * ```html
13149
+ * <lc-log-viewer [stream$]="logs$" autoScroll height="600px" variant="terminal" />
13150
+ * ```
13151
+ */
13152
+ class LogViewerComponent {
13153
+ scrollContainer;
13154
+ ngZone = inject(NgZone);
13155
+ streamSub;
13156
+ scrollListener;
13157
+ /** Controlled mode: array of log lines */
13158
+ lines = input([], ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
13159
+ /** Streaming mode: observable of log lines */
13160
+ stream$ = input(...(ngDevMode ? [undefined, { debugName: "stream$" }] : /* istanbul ignore next */ []));
13161
+ /** Maximum lines to keep in buffer */
13162
+ maxLines = input(10_000, ...(ngDevMode ? [{ debugName: "maxLines" }] : /* istanbul ignore next */ []));
13163
+ /** Auto-scroll to bottom on new lines */
13164
+ autoScroll = input(true, ...(ngDevMode ? [{ debugName: "autoScroll" }] : /* istanbul ignore next */ []));
13165
+ /** Show timestamps column */
13166
+ showTimestamps = input(true, ...(ngDevMode ? [{ debugName: "showTimestamps" }] : /* istanbul ignore next */ []));
13167
+ /** Show line numbers */
13168
+ showLineNumbers = input(false, ...(ngDevMode ? [{ debugName: "showLineNumbers" }] : /* istanbul ignore next */ []));
13169
+ /** Parse ANSI color codes */
13170
+ ansiColors = input(true, ...(ngDevMode ? [{ debugName: "ansiColors" }] : /* istanbul ignore next */ []));
13171
+ /** Filter by log levels */
13172
+ levelFilter = input(...(ngDevMode ? [undefined, { debugName: "levelFilter" }] : /* istanbul ignore next */ []));
13173
+ /** Search query to highlight */
13174
+ searchQuery = input(...(ngDevMode ? [undefined, { debugName: "searchQuery" }] : /* istanbul ignore next */ []));
13175
+ /** Container height */
13176
+ height = input('400px', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
13177
+ /** Visual variant */
13178
+ variant = input('log', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
13179
+ /** Emitted when a line is clicked */
13180
+ lineClick = output();
13181
+ /** Emitted on copy all */
13182
+ copyAll = output();
13183
+ /** Emitted when scroll state changes */
13184
+ scrollStateChange = output();
13185
+ /** Internal buffer of all lines */
13186
+ buffer = signal([], ...(ngDevMode ? [{ debugName: "buffer" }] : /* istanbul ignore next */ []));
13187
+ /** Whether stream is paused */
13188
+ paused = signal(false, ...(ngDevMode ? [{ debugName: "paused" }] : /* istanbul ignore next */ []));
13189
+ /** Whether user is at bottom of scroll */
13190
+ atBottom = signal(true, ...(ngDevMode ? [{ debugName: "atBottom" }] : /* istanbul ignore next */ []));
13191
+ /** Internal search input */
13192
+ internalSearch = signal('', ...(ngDevMode ? [{ debugName: "internalSearch" }] : /* istanbul ignore next */ []));
13193
+ /** Internal level filter */
13194
+ internalLevelFilter = signal(new Set(), ...(ngDevMode ? [{ debugName: "internalLevelFilter" }] : /* istanbul ignore next */ []));
13195
+ /** Show search bar */
13196
+ showSearch = signal(false, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
13197
+ /** Effective search query */
13198
+ effectiveSearch = computed(() => {
13199
+ return this.searchQuery() || this.internalSearch();
13200
+ }, ...(ngDevMode ? [{ debugName: "effectiveSearch" }] : /* istanbul ignore next */ []));
13201
+ /** Filtered lines for display */
13202
+ filteredLines = computed(() => {
13203
+ let lines = this.buffer();
13204
+ const levels = this.levelFilter();
13205
+ const internalFilter = this.internalLevelFilter();
13206
+ // Apply level filter
13207
+ if (levels?.length) {
13208
+ lines = lines.filter((l) => l.level && levels.includes(l.level));
13209
+ }
13210
+ else if (internalFilter.size > 0) {
13211
+ lines = lines.filter((l) => l.level && internalFilter.has(l.level));
13212
+ }
13213
+ return lines;
13214
+ }, ...(ngDevMode ? [{ debugName: "filteredLines" }] : /* istanbul ignore next */ []));
13215
+ /** Visible window of lines (virtualized) */
13216
+ scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
13217
+ LINE_HEIGHT = 22;
13218
+ visibleRange = computed(() => {
13219
+ const containerHeight = parseInt(this.height(), 10) || 400;
13220
+ const toolbarHeight = 40;
13221
+ const viewHeight = containerHeight - toolbarHeight;
13222
+ const total = this.filteredLines().length;
13223
+ const start = Math.floor(this.scrollTop() / this.LINE_HEIGHT);
13224
+ const visible = Math.ceil(viewHeight / this.LINE_HEIGHT) + 2;
13225
+ return {
13226
+ start: Math.max(0, start - 1),
13227
+ end: Math.min(total, start + visible + 1),
13228
+ total,
13229
+ totalHeight: total * this.LINE_HEIGHT,
13230
+ };
13231
+ }, ...(ngDevMode ? [{ debugName: "visibleRange" }] : /* istanbul ignore next */ []));
13232
+ visibleLines = computed(() => {
13233
+ const range = this.visibleRange();
13234
+ return this.filteredLines().slice(range.start, range.end).map((line, i) => ({
13235
+ ...line,
13236
+ _index: range.start + i,
13237
+ }));
13238
+ }, ...(ngDevMode ? [{ debugName: "visibleLines" }] : /* istanbul ignore next */ []));
13239
+ containerClasses = computed(() => {
13240
+ return `lc-log-viewer lc-log-viewer--${this.variant()}`;
13241
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
13242
+ /** Line counts per level for toolbar */
13243
+ levelCounts = computed(() => {
13244
+ const buf = this.buffer();
13245
+ const counts = { debug: 0, info: 0, warn: 0, error: 0 };
13246
+ for (const line of buf) {
13247
+ if (line.level && line.level in counts)
13248
+ counts[line.level]++;
13249
+ }
13250
+ return counts;
13251
+ }, ...(ngDevMode ? [{ debugName: "levelCounts" }] : /* istanbul ignore next */ []));
13252
+ constructor() {
13253
+ // Sync controlled lines to buffer
13254
+ effect(() => {
13255
+ const input = this.lines();
13256
+ if (input.length > 0) {
13257
+ this.buffer.set(input.slice(-this.maxLines()));
13258
+ }
13259
+ });
13260
+ // Subscribe to stream$
13261
+ effect(() => {
13262
+ const stream = this.stream$();
13263
+ this.streamSub?.unsubscribe();
13264
+ if (stream) {
13265
+ this.streamSub = stream.subscribe((data) => {
13266
+ if (this.paused())
13267
+ return;
13268
+ const newLines = Array.isArray(data) ? data : [data];
13269
+ this.buffer.update((buf) => {
13270
+ const combined = [...buf, ...newLines];
13271
+ return combined.length > this.maxLines()
13272
+ ? combined.slice(-this.maxLines())
13273
+ : combined;
13274
+ });
13275
+ });
13276
+ }
13277
+ });
13278
+ }
13279
+ ngAfterViewInit() {
13280
+ if (this.scrollContainer) {
13281
+ this.ngZone.runOutsideAngular(() => {
13282
+ this.scrollListener = () => this.onScroll();
13283
+ this.scrollContainer.nativeElement.addEventListener('scroll', this.scrollListener, { passive: true });
13284
+ });
13285
+ }
13286
+ }
13287
+ ngOnDestroy() {
13288
+ this.streamSub?.unsubscribe();
13289
+ if (this.scrollContainer && this.scrollListener) {
13290
+ this.scrollContainer.nativeElement.removeEventListener('scroll', this.scrollListener);
13291
+ }
13292
+ }
13293
+ onScroll() {
13294
+ const el = this.scrollContainer?.nativeElement;
13295
+ if (!el)
13296
+ return;
13297
+ this.scrollTop.set(el.scrollTop);
13298
+ const wasAtBottom = this.atBottom();
13299
+ const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30;
13300
+ if (wasAtBottom !== isAtBottom) {
13301
+ this.ngZone.run(() => {
13302
+ this.atBottom.set(isAtBottom);
13303
+ this.scrollStateChange.emit({ atBottom: isAtBottom });
13304
+ });
13305
+ }
13306
+ else {
13307
+ this.scrollTop.set(el.scrollTop);
13308
+ }
13309
+ }
13310
+ scrollToBottom() {
13311
+ const el = this.scrollContainer?.nativeElement;
13312
+ if (el) {
13313
+ el.scrollTop = el.scrollHeight;
13314
+ this.atBottom.set(true);
13315
+ }
13316
+ }
13317
+ togglePause() {
13318
+ this.paused.update((v) => !v);
13319
+ }
13320
+ clearBuffer() {
13321
+ this.buffer.set([]);
13322
+ }
13323
+ async onCopyAll() {
13324
+ const text = this.filteredLines()
13325
+ .map((l) => {
13326
+ const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';
13327
+ const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';
13328
+ return `${ts}${lvl}${l.text}`;
13329
+ })
13330
+ .join('\n');
13331
+ await navigator.clipboard.writeText(text);
13332
+ this.copyAll.emit(text);
13333
+ }
13334
+ toggleSearch() {
13335
+ this.showSearch.update((v) => !v);
13336
+ if (!this.showSearch()) {
13337
+ this.internalSearch.set('');
13338
+ }
13339
+ }
13340
+ toggleLevelFilter(level) {
13341
+ this.internalLevelFilter.update((set) => {
13342
+ const newSet = new Set(set);
13343
+ if (newSet.has(level)) {
13344
+ newSet.delete(level);
13345
+ }
13346
+ else {
13347
+ newSet.add(level);
13348
+ }
13349
+ return newSet;
13350
+ });
13351
+ }
13352
+ onLineClick(line) {
13353
+ this.lineClick.emit(line);
13354
+ }
13355
+ onKeydown(event) {
13356
+ if (event.key === '/' && !this.showSearch()) {
13357
+ event.preventDefault();
13358
+ this.toggleSearch();
13359
+ }
13360
+ if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {
13361
+ this.scrollContainer?.nativeElement.scrollTo(0, 0);
13362
+ }
13363
+ if (event.key === 'G' && !this.showSearch()) {
13364
+ this.scrollToBottom();
13365
+ }
13366
+ }
13367
+ formatTimestamp(date) {
13368
+ if (!date)
13369
+ return '';
13370
+ return date.toLocaleTimeString('en-US', { hour12: false });
13371
+ }
13372
+ parseAnsi(text) {
13373
+ if (!this.ansiColors())
13374
+ return this.escapeHtml(text);
13375
+ const escaped = this.escapeHtml(text);
13376
+ // Replace ANSI codes with spans
13377
+ return escaped
13378
+ .replace(/\x1b\[0m/g, '</span>')
13379
+ .replace(/\x1b\[1m/g, '<span class="ansi-bold">')
13380
+ .replace(/\x1b\[3m/g, '<span class="ansi-italic">')
13381
+ .replace(/\x1b\[30m/g, '<span class="ansi-black">')
13382
+ .replace(/\x1b\[31m/g, '<span class="ansi-red">')
13383
+ .replace(/\x1b\[32m/g, '<span class="ansi-green">')
13384
+ .replace(/\x1b\[33m/g, '<span class="ansi-yellow">')
13385
+ .replace(/\x1b\[34m/g, '<span class="ansi-blue">')
13386
+ .replace(/\x1b\[35m/g, '<span class="ansi-magenta">')
13387
+ .replace(/\x1b\[36m/g, '<span class="ansi-cyan">')
13388
+ .replace(/\x1b\[37m/g, '<span class="ansi-white">')
13389
+ .replace(/\x1b\[90m/g, '<span class="ansi-gray">')
13390
+ .replace(/\x1b\[91m/g, '<span class="ansi-bright-red">')
13391
+ .replace(/\x1b\[92m/g, '<span class="ansi-bright-green">')
13392
+ .replace(/\x1b\[93m/g, '<span class="ansi-bright-yellow">')
13393
+ .replace(/\x1b\[94m/g, '<span class="ansi-bright-blue">')
13394
+ .replace(/\x1b\[95m/g, '<span class="ansi-bright-magenta">')
13395
+ .replace(/\x1b\[96m/g, '<span class="ansi-bright-cyan">')
13396
+ .replace(/\x1b\[97m/g, '<span class="ansi-bright-white">')
13397
+ .replace(/\x1b\[\d+m/g, ''); // strip unrecognized codes
13398
+ }
13399
+ highlightSearch(html) {
13400
+ const query = this.effectiveSearch();
13401
+ if (!query)
13402
+ return html;
13403
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13404
+ return html.replace(new RegExp(`(${escaped})`, 'gi'), '<mark class="lc-log-viewer__match">$1</mark>');
13405
+ }
13406
+ escapeHtml(text) {
13407
+ return text
13408
+ .replace(/&/g, '&amp;')
13409
+ .replace(/</g, '&lt;')
13410
+ .replace(/>/g, '&gt;');
13411
+ }
13412
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: LogViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13413
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: LogViewerComponent, isStandalone: true, selector: "lc-log-viewer", inputs: { lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, stream$: { classPropertyName: "stream$", publicName: "stream$", isSignal: true, isRequired: false, transformFunction: null }, maxLines: { classPropertyName: "maxLines", publicName: "maxLines", isSignal: true, isRequired: false, transformFunction: null }, autoScroll: { classPropertyName: "autoScroll", publicName: "autoScroll", isSignal: true, isRequired: false, transformFunction: null }, showTimestamps: { classPropertyName: "showTimestamps", publicName: "showTimestamps", isSignal: true, isRequired: false, transformFunction: null }, showLineNumbers: { classPropertyName: "showLineNumbers", publicName: "showLineNumbers", isSignal: true, isRequired: false, transformFunction: null }, ansiColors: { classPropertyName: "ansiColors", publicName: "ansiColors", isSignal: true, isRequired: false, transformFunction: null }, levelFilter: { classPropertyName: "levelFilter", publicName: "levelFilter", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lineClick: "lineClick", copyAll: "copyAll", scrollStateChange: "scrollStateChange" }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, 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() ? 'Resume' : 'Pause'\"\n >\n <lc-icon [name]=\"paused() ? 'play' : 'pause'\" size=\"xs\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n title=\"Clear\"\n >\n <lc-icon name=\"trash\" size=\"xs\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n title=\"Copy all\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" />\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 title=\"Search (/)\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" />\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 (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} errors\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 (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} warnings\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} lines</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\" />\n <input\n type=\"text\"\n class=\"lc-log-viewer__search-input\"\n placeholder=\"Search logs...\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set($any($event.target).value)\"\n autofocus\n />\n </div>\n }\n\n <!-- Log content (virtualized) -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\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 * LINE_HEIGHT) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\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 (click)=\"onLineClick(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]=\"highlightSearch(parseAnsi(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\" />\n Jump to bottom\n </button>\n }\n</div>\n", styles: [".lc-log-viewer{display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--radius-md, .375rem);overflow:hidden;font-family:var(--font-family-mono, \"Menlo\", \"Monaco\", \"Courier New\", monospace);font-size:.8125rem;line-height:1}.lc-log-viewer:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-log-viewer--log{background-color:var(--color-background, #ffffff);color:var(--color-text, #111827)}.lc-log-viewer--terminal{background-color:#1e1e2e;color:#cdd6f4;border-color:#313244}.lc-log-viewer__toolbar{display:flex;align-items:center;justify-content:space-between;padding:.375rem .5rem;border-bottom:1px solid var(--color-divider, #e5e7eb);gap:.5rem;flex-shrink:0}.lc-log-viewer--terminal .lc-log-viewer__toolbar{border-bottom-color:#313244;background-color:#181825}.lc-log-viewer__toolbar-left,.lc-log-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem}.lc-log-viewer__tool-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:var(--radius-sm, .25rem);cursor:pointer;color:inherit;opacity:.7;transition:opacity .15s,background-color .15s}.lc-log-viewer__tool-btn:hover{opacity:1;background-color:#80808026}.lc-log-viewer__tool-btn--active{opacity:1;background-color:#80808033}.lc-log-viewer__level-pill{font-size:.6875rem;padding:.125rem .5rem;border-radius:9999px;border:none;cursor:pointer;font-family:var(--font-family-sans);transition:opacity .15s;opacity:.6}.lc-log-viewer__level-pill--active{opacity:1}.lc-log-viewer__level-pill--error{background-color:#fecaca;color:#991b1b}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#fef3c7;color:#92400e}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}.lc-log-viewer__line-count{font-size:.6875rem;opacity:.5;font-family:var(--font-family-sans);padding:0 .25rem}.lc-log-viewer__search{display:flex;align-items:center;gap:.375rem;padding:.25rem .5rem;border-bottom:1px solid var(--color-divider, #e5e7eb);flex-shrink:0}.lc-log-viewer--terminal .lc-log-viewer__search{border-bottom-color:#313244;background-color:#181825}.lc-log-viewer__search-input{flex:1;background:transparent;border:none;outline:none;color:inherit;font-family:inherit;font-size:inherit}.lc-log-viewer__search-input::placeholder{opacity:.4}.lc-log-viewer__content{flex:1;overflow-y:auto;overflow-x:auto;position:relative;min-height:0}.lc-log-viewer__spacer{position:relative;width:100%}.lc-log-viewer__window{position:absolute;top:0;left:0;right:0}.lc-log-viewer__line{display:flex;align-items:baseline;gap:.625rem;padding:1px .75rem;height:22px;white-space:nowrap;cursor:pointer;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--debug{opacity:.6}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#fffbeb}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#fef2f2}.lc-log-viewer--terminal .lc-log-viewer__line--error{background-color:#f38ba814}.lc-log-viewer__line-num{color:inherit;opacity:.3;min-width:3ch;text-align:right;-webkit-user-select:none;user-select:none}.lc-log-viewer__timestamp{color:inherit;opacity:.5;min-width:12ch}.lc-log-viewer__level{min-width:5ch;font-weight:600}.lc-log-viewer__line--debug .lc-log-viewer__level{color:#9ca3af}.lc-log-viewer__line--info .lc-log-viewer__level{color:#3b82f6}.lc-log-viewer__line--warn .lc-log-viewer__level{color:#f59e0b}.lc-log-viewer__line--error .lc-log-viewer__level{color:#ef4444}.lc-log-viewer__line--debug .lc-log-viewer--terminal .lc-log-viewer__level{color:#6c7086}.lc-log-viewer__line--info .lc-log-viewer--terminal .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer__line--warn .lc-log-viewer--terminal .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer__line--error .lc-log-viewer--terminal .lc-log-viewer__level{color:#f38ba8}.lc-log-viewer__source{color:inherit;opacity:.5}.lc-log-viewer__text{flex:1;min-width:0}.lc-log-viewer__match{background-color:#fbbf2480;border-radius:2px;padding:0 1px}.lc-log-viewer--terminal .lc-log-viewer__match{background-color:#f9e2af40}.lc-log-viewer__jump-bottom{position:absolute;bottom:1rem;left:50%;transform:translate(-50%);display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:1px solid var(--color-divider, #e5e7eb);border-radius:9999px;background-color:var(--color-background, #ffffff);color:var(--color-text, #111827);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:0 2px 8px #00000026;z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-neutral-50, #f9fafb)}.lc-log-viewer--terminal .lc-log-viewer__jump-bottom{background-color:#313244;border-color:#45475a;color:#cdd6f4}.lc-log-viewer--terminal .lc-log-viewer__jump-bottom:hover{background-color:#45475a}.ansi-bold{font-weight:700}.ansi-italic{font-style:italic}.ansi-black{color:#1e1e2e}.ansi-red{color:#f38ba8}.ansi-green{color:#a6e3a1}.ansi-yellow{color:#f9e2af}.ansi-blue{color:#89b4fa}.ansi-magenta{color:#cba6f7}.ansi-cyan{color:#94e2d5}.ansi-white{color:#cdd6f4}.ansi-gray{color:#6c7086}.ansi-bright-red{color:#eba0ac}.ansi-bright-green{color:#a6e3a1}.ansi-bright-yellow{color:#f9e2af}.ansi-bright-blue{color:#89dceb}.ansi-bright-magenta{color:#f5c2e7}.ansi-bright-cyan{color:#94e2d5}.ansi-bright-white{color:#bac2de}.lc-log-viewer--log .ansi-black{color:#111827}.lc-log-viewer--log .ansi-red{color:#dc2626}.lc-log-viewer--log .ansi-green{color:#16a34a}.lc-log-viewer--log .ansi-yellow{color:#ca8a04}.lc-log-viewer--log .ansi-blue{color:#2563eb}.lc-log-viewer--log .ansi-magenta{color:#9333ea}.lc-log-viewer--log .ansi-cyan{color:#0891b2}.lc-log-viewer--log .ansi-white{color:#6b7280}.lc-log-viewer--log .ansi-gray{color:#9ca3af}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar{width:8px;height:8px}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-track{background:#181825}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-thumb{background:#45475a;border-radius:4px}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-thumb:hover{background:#585b70}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "lc-icon", inputs: ["name", "variant", "size", "color", "ariaLabel", "decorative"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13414
+ }
13415
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: LogViewerComponent, decorators: [{
13416
+ type: Component,
13417
+ args: [{ selector: 'lc-log-viewer', standalone: true, imports: [CommonModule, IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, 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() ? 'Resume' : 'Pause'\"\n >\n <lc-icon [name]=\"paused() ? 'play' : 'pause'\" size=\"xs\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n title=\"Clear\"\n >\n <lc-icon name=\"trash\" size=\"xs\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n title=\"Copy all\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" />\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 title=\"Search (/)\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" />\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 (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} errors\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 (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} warnings\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} lines</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\" />\n <input\n type=\"text\"\n class=\"lc-log-viewer__search-input\"\n placeholder=\"Search logs...\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set($any($event.target).value)\"\n autofocus\n />\n </div>\n }\n\n <!-- Log content (virtualized) -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\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 * LINE_HEIGHT) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\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 (click)=\"onLineClick(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]=\"highlightSearch(parseAnsi(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\" />\n Jump to bottom\n </button>\n }\n</div>\n", styles: [".lc-log-viewer{display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--radius-md, .375rem);overflow:hidden;font-family:var(--font-family-mono, \"Menlo\", \"Monaco\", \"Courier New\", monospace);font-size:.8125rem;line-height:1}.lc-log-viewer:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-log-viewer--log{background-color:var(--color-background, #ffffff);color:var(--color-text, #111827)}.lc-log-viewer--terminal{background-color:#1e1e2e;color:#cdd6f4;border-color:#313244}.lc-log-viewer__toolbar{display:flex;align-items:center;justify-content:space-between;padding:.375rem .5rem;border-bottom:1px solid var(--color-divider, #e5e7eb);gap:.5rem;flex-shrink:0}.lc-log-viewer--terminal .lc-log-viewer__toolbar{border-bottom-color:#313244;background-color:#181825}.lc-log-viewer__toolbar-left,.lc-log-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem}.lc-log-viewer__tool-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:var(--radius-sm, .25rem);cursor:pointer;color:inherit;opacity:.7;transition:opacity .15s,background-color .15s}.lc-log-viewer__tool-btn:hover{opacity:1;background-color:#80808026}.lc-log-viewer__tool-btn--active{opacity:1;background-color:#80808033}.lc-log-viewer__level-pill{font-size:.6875rem;padding:.125rem .5rem;border-radius:9999px;border:none;cursor:pointer;font-family:var(--font-family-sans);transition:opacity .15s;opacity:.6}.lc-log-viewer__level-pill--active{opacity:1}.lc-log-viewer__level-pill--error{background-color:#fecaca;color:#991b1b}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#fef3c7;color:#92400e}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}.lc-log-viewer__line-count{font-size:.6875rem;opacity:.5;font-family:var(--font-family-sans);padding:0 .25rem}.lc-log-viewer__search{display:flex;align-items:center;gap:.375rem;padding:.25rem .5rem;border-bottom:1px solid var(--color-divider, #e5e7eb);flex-shrink:0}.lc-log-viewer--terminal .lc-log-viewer__search{border-bottom-color:#313244;background-color:#181825}.lc-log-viewer__search-input{flex:1;background:transparent;border:none;outline:none;color:inherit;font-family:inherit;font-size:inherit}.lc-log-viewer__search-input::placeholder{opacity:.4}.lc-log-viewer__content{flex:1;overflow-y:auto;overflow-x:auto;position:relative;min-height:0}.lc-log-viewer__spacer{position:relative;width:100%}.lc-log-viewer__window{position:absolute;top:0;left:0;right:0}.lc-log-viewer__line{display:flex;align-items:baseline;gap:.625rem;padding:1px .75rem;height:22px;white-space:nowrap;cursor:pointer;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--debug{opacity:.6}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#fffbeb}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#fef2f2}.lc-log-viewer--terminal .lc-log-viewer__line--error{background-color:#f38ba814}.lc-log-viewer__line-num{color:inherit;opacity:.3;min-width:3ch;text-align:right;-webkit-user-select:none;user-select:none}.lc-log-viewer__timestamp{color:inherit;opacity:.5;min-width:12ch}.lc-log-viewer__level{min-width:5ch;font-weight:600}.lc-log-viewer__line--debug .lc-log-viewer__level{color:#9ca3af}.lc-log-viewer__line--info .lc-log-viewer__level{color:#3b82f6}.lc-log-viewer__line--warn .lc-log-viewer__level{color:#f59e0b}.lc-log-viewer__line--error .lc-log-viewer__level{color:#ef4444}.lc-log-viewer__line--debug .lc-log-viewer--terminal .lc-log-viewer__level{color:#6c7086}.lc-log-viewer__line--info .lc-log-viewer--terminal .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer__line--warn .lc-log-viewer--terminal .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer__line--error .lc-log-viewer--terminal .lc-log-viewer__level{color:#f38ba8}.lc-log-viewer__source{color:inherit;opacity:.5}.lc-log-viewer__text{flex:1;min-width:0}.lc-log-viewer__match{background-color:#fbbf2480;border-radius:2px;padding:0 1px}.lc-log-viewer--terminal .lc-log-viewer__match{background-color:#f9e2af40}.lc-log-viewer__jump-bottom{position:absolute;bottom:1rem;left:50%;transform:translate(-50%);display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:1px solid var(--color-divider, #e5e7eb);border-radius:9999px;background-color:var(--color-background, #ffffff);color:var(--color-text, #111827);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:0 2px 8px #00000026;z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-neutral-50, #f9fafb)}.lc-log-viewer--terminal .lc-log-viewer__jump-bottom{background-color:#313244;border-color:#45475a;color:#cdd6f4}.lc-log-viewer--terminal .lc-log-viewer__jump-bottom:hover{background-color:#45475a}.ansi-bold{font-weight:700}.ansi-italic{font-style:italic}.ansi-black{color:#1e1e2e}.ansi-red{color:#f38ba8}.ansi-green{color:#a6e3a1}.ansi-yellow{color:#f9e2af}.ansi-blue{color:#89b4fa}.ansi-magenta{color:#cba6f7}.ansi-cyan{color:#94e2d5}.ansi-white{color:#cdd6f4}.ansi-gray{color:#6c7086}.ansi-bright-red{color:#eba0ac}.ansi-bright-green{color:#a6e3a1}.ansi-bright-yellow{color:#f9e2af}.ansi-bright-blue{color:#89dceb}.ansi-bright-magenta{color:#f5c2e7}.ansi-bright-cyan{color:#94e2d5}.ansi-bright-white{color:#bac2de}.lc-log-viewer--log .ansi-black{color:#111827}.lc-log-viewer--log .ansi-red{color:#dc2626}.lc-log-viewer--log .ansi-green{color:#16a34a}.lc-log-viewer--log .ansi-yellow{color:#ca8a04}.lc-log-viewer--log .ansi-blue{color:#2563eb}.lc-log-viewer--log .ansi-magenta{color:#9333ea}.lc-log-viewer--log .ansi-cyan{color:#0891b2}.lc-log-viewer--log .ansi-white{color:#6b7280}.lc-log-viewer--log .ansi-gray{color:#9ca3af}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar{width:8px;height:8px}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-track{background:#181825}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-thumb{background:#45475a;border-radius:4px}.lc-log-viewer--terminal .lc-log-viewer__content::-webkit-scrollbar-thumb:hover{background:#585b70}\n"] }]
13418
+ }], ctorParameters: () => [], propDecorators: { scrollContainer: [{
13419
+ type: ViewChild,
13420
+ args: ['scrollContainer']
13421
+ }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], stream$: [{ type: i0.Input, args: [{ isSignal: true, alias: "stream$", required: false }] }], maxLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLines", required: false }] }], autoScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScroll", required: false }] }], showTimestamps: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimestamps", required: false }] }], showLineNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLineNumbers", required: false }] }], ansiColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "ansiColors", required: false }] }], levelFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelFilter", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], lineClick: [{ type: i0.Output, args: ["lineClick"] }], copyAll: [{ type: i0.Output, args: ["copyAll"] }], scrollStateChange: [{ type: i0.Output, args: ["scrollStateChange"] }] } });
13422
+
13423
+ /**
13424
+ * Confirm dialog component for confirming user actions.
13425
+ *
13426
+ * Builds on `<lc-modal>` to provide a standardized confirmation pattern
13427
+ * with optional destructive variant and text-matching confirmation.
13428
+ *
13429
+ * @example
13430
+ * ```html
13431
+ * <lc-confirm-dialog
13432
+ * [open]="showConfirm()"
13433
+ * variant="destructive"
13434
+ * title="Delete project?"
13435
+ * message="This cannot be undone."
13436
+ * (confirmed)="onDelete()"
13437
+ * (cancelled)="showConfirm.set(false)"
13438
+ * />
13439
+ * ```
13440
+ */
13441
+ class ConfirmDialogComponent {
13442
+ confirmInput;
13443
+ /** Whether the dialog is open */
13444
+ open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
13445
+ /** Dialog variant */
13446
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
13447
+ /** Dialog title */
13448
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
13449
+ /** Dialog message */
13450
+ message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
13451
+ /** Confirm button label */
13452
+ confirmLabel = input('Confirm', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
13453
+ /** Cancel button label */
13454
+ cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
13455
+ /** Icon name (auto-set by variant if not provided) */
13456
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
13457
+ /** Require text match to enable confirm */
13458
+ requireText = input(...(ngDevMode ? [undefined, { debugName: "requireText" }] : /* istanbul ignore next */ []));
13459
+ /** Emitted when user confirms */
13460
+ confirmed = output();
13461
+ /** Emitted when user cancels */
13462
+ cancelled = output();
13463
+ /** Internal text input value */
13464
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
13465
+ /** Resolved icon name */
13466
+ resolvedIcon = computed(() => {
13467
+ const icon = this.icon();
13468
+ if (icon)
13469
+ return icon;
13470
+ switch (this.variant()) {
13471
+ case 'destructive':
13472
+ return 'exclamation-triangle';
13473
+ case 'warning':
13474
+ return 'exclamation-circle';
13475
+ default:
13476
+ return 'question-mark-circle';
13477
+ }
13478
+ }, ...(ngDevMode ? [{ debugName: "resolvedIcon" }] : /* istanbul ignore next */ []));
13479
+ /** Resolved icon color */
13480
+ iconColor = computed(() => {
13481
+ switch (this.variant()) {
13482
+ case 'destructive':
13483
+ return 'var(--color-error-default, #ef4444)';
13484
+ case 'warning':
13485
+ return 'var(--color-warning-default, #f59e0b)';
13486
+ default:
13487
+ return 'var(--color-primary-600, #2563eb)';
13488
+ }
13489
+ }, ...(ngDevMode ? [{ debugName: "iconColor" }] : /* istanbul ignore next */ []));
13490
+ /** Confirm button variant */
13491
+ confirmButtonVariant = computed(() => {
13492
+ switch (this.variant()) {
13493
+ case 'destructive':
13494
+ return 'danger';
13495
+ case 'warning':
13496
+ return 'warning';
13497
+ default:
13498
+ return 'primary';
13499
+ }
13500
+ }, ...(ngDevMode ? [{ debugName: "confirmButtonVariant" }] : /* istanbul ignore next */ []));
13501
+ /** Whether confirm is allowed (text match check) */
13502
+ confirmAllowed = computed(() => {
13503
+ const req = this.requireText();
13504
+ if (!req)
13505
+ return true;
13506
+ return this.inputValue() === req.expected;
13507
+ }, ...(ngDevMode ? [{ debugName: "confirmAllowed" }] : /* istanbul ignore next */ []));
13508
+ constructor() {
13509
+ // Reset input when dialog opens
13510
+ effect(() => {
13511
+ if (this.open()) {
13512
+ this.inputValue.set('');
13513
+ }
13514
+ });
13515
+ }
13516
+ onConfirm() {
13517
+ if (!this.confirmAllowed())
13518
+ return;
13519
+ this.confirmed.emit();
13520
+ }
13521
+ onCancel() {
13522
+ this.cancelled.emit();
13523
+ }
13524
+ onModalClose() {
13525
+ this.cancelled.emit();
13526
+ }
13527
+ onInputChange(value) {
13528
+ this.inputValue.set(String(value));
13529
+ }
13530
+ onKeydown(event) {
13531
+ if (event.key === 'Enter' && this.confirmAllowed()) {
13532
+ event.preventDefault();
13533
+ this.onConfirm();
13534
+ }
13535
+ }
13536
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13537
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: ConfirmDialogComponent, isStandalone: true, selector: "lc-confirm-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, confirmLabel: { classPropertyName: "confirmLabel", publicName: "confirmLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, requireText: { classPropertyName: "requireText", publicName: "requireText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, viewQueries: [{ propertyName: "confirmInput", first: true, predicate: ["confirmInput"], descendants: true }], ngImport: i0, template: "<lc-modal\n [open]=\"open()\"\n size=\"sm\"\n [showCloseButton]=\"true\"\n (openChange)=\"$event ? null : onModalClose()\"\n (keydown)=\"onKeydown($event)\"\n>\n <div slot=\"header\">\n <div class=\"lc-confirm__header\">\n <lc-icon\n [name]=\"resolvedIcon()\"\n size=\"md\"\n [color]=\"iconColor()\"\n />\n <h2>{{ title() }}</h2>\n </div>\n </div>\n\n <div slot=\"body\">\n <p class=\"lc-confirm__message\">{{ message() }}</p>\n\n @if (requireText(); as req) {\n <div class=\"lc-confirm__require-text\">\n <label class=\"lc-confirm__require-label\">{{ req.prompt }}</label>\n <lc-input\n #confirmInput\n [placeholder]=\"req.expected\"\n (valueChange)=\"onInputChange($event)\"\n />\n </div>\n }\n </div>\n\n <div slot=\"footer\" class=\"lc-confirm__footer\">\n <lc-button\n variant=\"ghost\"\n (clicked)=\"onCancel()\"\n >\n {{ cancelLabel() }}\n </lc-button>\n <lc-button\n [variant]=\"confirmButtonVariant()\"\n [disabled]=\"!confirmAllowed()\"\n (clicked)=\"onConfirm()\"\n >\n {{ confirmLabel() }}\n </lc-button>\n </div>\n</lc-modal>\n", styles: [".lc-confirm__header{display:flex;align-items:center;gap:.5rem}.lc-confirm__header h2{margin:0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-text, #111827)}.lc-confirm__message{margin:0;color:var(--color-text-secondary, #4b5563);font-size:var(--font-size-sm, .875rem);line-height:1.6}.lc-confirm__require-text{margin-top:1rem}.lc-confirm__require-label{display:block;font-size:var(--font-size-sm, .875rem);font-weight:500;color:var(--color-text, #111827);margin-bottom:.375rem}.lc-confirm__footer{display:flex;gap:.5rem;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: IconComponent, selector: "lc-icon", inputs: ["name", "variant", "size", "color", "ariaLabel", "decorative"] }, { kind: "component", type: ButtonComponent, selector: "lc-button", inputs: ["variant", "size", "disabled", "loading", "isLoading", "iconOnly", "fullWidth", "ariaLabel", "type"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: ModalComponent, selector: "lc-modal", inputs: ["open", "size", "closeOnBackdropClick", "closeOnEscape", "showCloseButton", "ariaLabel", "ariaLabelledBy", "ariaDescribedBy"], outputs: ["modalOpened", "modalClosed", "openChange", "backdropClicked"] }, { kind: "component", type: InputComponent, selector: "lc-input", inputs: ["label", "placeholder", "type", "size", "disabled", "readonly", "required", "error", "helperText", "iconBefore", "iconAfter", "maxLength", "showCharCount", "ariaLabel"], outputs: ["valueChange", "focused", "blurred", "enterPressed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13538
+ }
13539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
13540
+ type: Component,
13541
+ args: [{ selector: 'lc-confirm-dialog', standalone: true, imports: [CommonModule, FormsModule, IconComponent, ButtonComponent, ModalComponent, InputComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<lc-modal\n [open]=\"open()\"\n size=\"sm\"\n [showCloseButton]=\"true\"\n (openChange)=\"$event ? null : onModalClose()\"\n (keydown)=\"onKeydown($event)\"\n>\n <div slot=\"header\">\n <div class=\"lc-confirm__header\">\n <lc-icon\n [name]=\"resolvedIcon()\"\n size=\"md\"\n [color]=\"iconColor()\"\n />\n <h2>{{ title() }}</h2>\n </div>\n </div>\n\n <div slot=\"body\">\n <p class=\"lc-confirm__message\">{{ message() }}</p>\n\n @if (requireText(); as req) {\n <div class=\"lc-confirm__require-text\">\n <label class=\"lc-confirm__require-label\">{{ req.prompt }}</label>\n <lc-input\n #confirmInput\n [placeholder]=\"req.expected\"\n (valueChange)=\"onInputChange($event)\"\n />\n </div>\n }\n </div>\n\n <div slot=\"footer\" class=\"lc-confirm__footer\">\n <lc-button\n variant=\"ghost\"\n (clicked)=\"onCancel()\"\n >\n {{ cancelLabel() }}\n </lc-button>\n <lc-button\n [variant]=\"confirmButtonVariant()\"\n [disabled]=\"!confirmAllowed()\"\n (clicked)=\"onConfirm()\"\n >\n {{ confirmLabel() }}\n </lc-button>\n </div>\n</lc-modal>\n", styles: [".lc-confirm__header{display:flex;align-items:center;gap:.5rem}.lc-confirm__header h2{margin:0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-text, #111827)}.lc-confirm__message{margin:0;color:var(--color-text-secondary, #4b5563);font-size:var(--font-size-sm, .875rem);line-height:1.6}.lc-confirm__require-text{margin-top:1rem}.lc-confirm__require-label{display:block;font-size:var(--font-size-sm, .875rem);font-weight:500;color:var(--color-text, #111827);margin-bottom:.375rem}.lc-confirm__footer{display:flex;gap:.5rem;justify-content:flex-end}\n"] }]
13542
+ }], ctorParameters: () => [], propDecorators: { confirmInput: [{
13543
+ type: ViewChild,
13544
+ args: ['confirmInput']
13545
+ }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], confirmLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], requireText: [{ type: i0.Input, args: [{ isSignal: true, alias: "requireText", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
13546
+
13547
+ /**
13548
+ * Imperative confirm dialog service.
13549
+ *
13550
+ * @example
13551
+ * ```ts
13552
+ * const ok = await this.confirmService.confirm({
13553
+ * title: 'Delete item?',
13554
+ * message: 'This cannot be undone.',
13555
+ * });
13556
+ * if (ok) { ... }
13557
+ * ```
13558
+ */
13559
+ class ConfirmService {
13560
+ appRef = inject(ApplicationRef);
13561
+ injector = inject(EnvironmentInjector);
13562
+ /**
13563
+ * Show a confirmation dialog and return a promise that resolves
13564
+ * to true (confirmed) or false (cancelled).
13565
+ */
13566
+ confirm(opts) {
13567
+ return new Promise((resolve) => {
13568
+ // Create a host element
13569
+ const hostEl = document.createElement('div');
13570
+ document.body.appendChild(hostEl);
13571
+ const componentRef = createComponent(ConfirmDialogComponent, {
13572
+ hostElement: hostEl,
13573
+ environmentInjector: this.injector,
13574
+ });
13575
+ // Set inputs
13576
+ componentRef.setInput('open', true);
13577
+ componentRef.setInput('title', opts.title);
13578
+ componentRef.setInput('message', opts.message);
13579
+ if (opts.confirmLabel)
13580
+ componentRef.setInput('confirmLabel', opts.confirmLabel);
13581
+ if (opts.cancelLabel)
13582
+ componentRef.setInput('cancelLabel', opts.cancelLabel);
13583
+ if (opts.variant)
13584
+ componentRef.setInput('variant', opts.variant);
13585
+ if (opts.icon)
13586
+ componentRef.setInput('icon', opts.icon);
13587
+ if (opts.requireText)
13588
+ componentRef.setInput('requireText', opts.requireText);
13589
+ const cleanup = () => {
13590
+ this.appRef.detachView(componentRef.hostView);
13591
+ componentRef.destroy();
13592
+ hostEl.remove();
13593
+ };
13594
+ componentRef.instance.confirmed.subscribe(() => {
13595
+ cleanup();
13596
+ resolve(true);
13597
+ });
13598
+ componentRef.instance.cancelled.subscribe(() => {
13599
+ cleanup();
13600
+ resolve(false);
13601
+ });
13602
+ this.appRef.attachView(componentRef.hostView);
13603
+ });
13604
+ }
13605
+ /**
13606
+ * Show a destructive confirmation dialog.
13607
+ * Shorthand for `confirm({ ...opts, variant: 'destructive' })`.
13608
+ */
13609
+ destructive(opts) {
13610
+ return this.confirm({ ...opts, variant: 'destructive' });
13611
+ }
13612
+ /**
13613
+ * Show a warning confirmation dialog.
13614
+ * Shorthand for `confirm({ ...opts, variant: 'warning' })`.
13615
+ */
13616
+ warning(opts) {
13617
+ return this.confirm({ ...opts, variant: 'warning' });
13618
+ }
13619
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13620
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, providedIn: 'root' });
13621
+ }
13622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, decorators: [{
13623
+ type: Injectable,
13624
+ args: [{ providedIn: 'root' }]
13625
+ }] });
13626
+
13627
+ /**
13628
+ * Combobox / async autocomplete component.
13629
+ *
13630
+ * Supports free-text input with sync/async option suggestions,
13631
+ * single/multiple selection, create new entries, and keyboard navigation.
13632
+ *
13633
+ * @example
13634
+ * ```html
13635
+ * <lc-combobox
13636
+ * label="Assign to user"
13637
+ * [loadOptions]="searchUsers"
13638
+ * [(value)]="assignee"
13639
+ * allowCreate
13640
+ * />
13641
+ * ```
13642
+ */
13643
+ class ComboboxComponent {
13644
+ inputEl;
13645
+ elRef = inject(ElementRef);
13646
+ querySubject = new Subject();
13647
+ asyncSub;
13648
+ /** Sync options */
13649
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
13650
+ /** Async option loader */
13651
+ loadOptions = input(...(ngDevMode ? [undefined, { debugName: "loadOptions" }] : /* istanbul ignore next */ []));
13652
+ /** Debounce for async queries */
13653
+ debounceMs = input(250, ...(ngDevMode ? [{ debugName: "debounceMs" }] : /* istanbul ignore next */ []));
13654
+ /** Minimum characters before triggering search */
13655
+ minChars = input(1, ...(ngDevMode ? [{ debugName: "minChars" }] : /* istanbul ignore next */ []));
13656
+ /** Placeholder text */
13657
+ placeholder = input('Search…', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
13658
+ /** Label */
13659
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
13660
+ /** Helper text */
13661
+ helperText = input(...(ngDevMode ? [undefined, { debugName: "helperText" }] : /* istanbul ignore next */ []));
13662
+ /** Error message */
13663
+ error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
13664
+ /** Disabled state */
13665
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
13666
+ /** Allow multiple selections */
13667
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
13668
+ /** Allow creating new entries */
13669
+ allowCreate = input(false, ...(ngDevMode ? [{ debugName: "allowCreate" }] : /* istanbul ignore next */ []));
13670
+ /** Show loading spinner (controlled) */
13671
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
13672
+ /** Empty state message */
13673
+ emptyMessage = input('No results', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
13674
+ /** Size */
13675
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
13676
+ /** Value change event */
13677
+ valueChange = output();
13678
+ /** Query change event */
13679
+ queryChange = output();
13680
+ /** Option selected event */
13681
+ optionSelected = output();
13682
+ /** Created event (allowCreate) */
13683
+ created = output();
13684
+ /** Internal state */
13685
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
13686
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
13687
+ highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : /* istanbul ignore next */ []));
13688
+ asyncOptions = signal([], ...(ngDevMode ? [{ debugName: "asyncOptions" }] : /* istanbul ignore next */ []));
13689
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
13690
+ /** Selected values */
13691
+ selectedSingle = signal(null, ...(ngDevMode ? [{ debugName: "selectedSingle" }] : /* istanbul ignore next */ []));
13692
+ selectedMultiple = signal([], ...(ngDevMode ? [{ debugName: "selectedMultiple" }] : /* istanbul ignore next */ []));
13693
+ /** Resolved visible options */
13694
+ visibleOptions = computed(() => {
13695
+ const q = this.query().toLowerCase();
13696
+ const loader = this.loadOptions();
13697
+ let opts;
13698
+ if (loader) {
13699
+ opts = this.asyncOptions();
13700
+ }
13701
+ else {
13702
+ opts = this.options();
13703
+ if (q.length >= this.minChars()) {
13704
+ opts = opts.filter((o) => o.label.toLowerCase().includes(q) ||
13705
+ o.value.toLowerCase().includes(q) ||
13706
+ (o.description && o.description.toLowerCase().includes(q)));
13707
+ }
13708
+ }
13709
+ // Remove already selected in multiple mode
13710
+ if (this.multiple()) {
13711
+ const selected = new Set(this.selectedMultiple().map((s) => s.value));
13712
+ opts = opts.filter((o) => !selected.has(o.value));
13713
+ }
13714
+ return opts;
13715
+ }, ...(ngDevMode ? [{ debugName: "visibleOptions" }] : /* istanbul ignore next */ []));
13716
+ /** Grouped options */
13717
+ groupedOptions = computed(() => {
13718
+ const opts = this.visibleOptions();
13719
+ const groups = new Map();
13720
+ for (const opt of opts) {
13721
+ const group = opt.group || '';
13722
+ if (!groups.has(group))
13723
+ groups.set(group, []);
13724
+ groups.get(group).push(opt);
13725
+ }
13726
+ return Array.from(groups.entries()).map(([label, items]) => ({
13727
+ label,
13728
+ items,
13729
+ }));
13730
+ }, ...(ngDevMode ? [{ debugName: "groupedOptions" }] : /* istanbul ignore next */ []));
13731
+ /** Flat list for keyboard nav */
13732
+ flatOptions = computed(() => {
13733
+ return this.groupedOptions().flatMap((g) => g.items);
13734
+ }, ...(ngDevMode ? [{ debugName: "flatOptions" }] : /* istanbul ignore next */ []));
13735
+ /** Whether to show "Create" option */
13736
+ showCreateOption = computed(() => {
13737
+ if (!this.allowCreate())
13738
+ return false;
13739
+ const q = this.query().trim();
13740
+ if (!q)
13741
+ return false;
13742
+ const exact = this.visibleOptions().some((o) => o.label.toLowerCase() === q.toLowerCase());
13743
+ return !exact;
13744
+ }, ...(ngDevMode ? [{ debugName: "showCreateOption" }] : /* istanbul ignore next */ []));
13745
+ /** Container classes */
13746
+ containerClasses = computed(() => {
13747
+ const classes = [`lc-combobox`, `lc-combobox--${this.size()}`];
13748
+ if (this.disabled())
13749
+ classes.push('lc-combobox--disabled');
13750
+ if (this.error())
13751
+ classes.push('lc-combobox--error');
13752
+ if (this.isOpen())
13753
+ classes.push('lc-combobox--open');
13754
+ return classes.join(' ');
13755
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
13756
+ // ControlValueAccessor
13757
+ onChange = () => { };
13758
+ onTouched = () => { };
13759
+ constructor() {
13760
+ // Async loading pipe
13761
+ this.asyncSub = this.querySubject
13762
+ .pipe(debounceTime(this.debounceMs()), switchMap((q) => {
13763
+ const loader = this.loadOptions();
13764
+ if (!loader || q.length < this.minChars()) {
13765
+ return of([]);
13766
+ }
13767
+ this.isLoading.set(true);
13768
+ return loader(q);
13769
+ }))
13770
+ .subscribe((results) => {
13771
+ this.asyncOptions.set(results);
13772
+ this.isLoading.set(false);
13773
+ });
13774
+ }
13775
+ ngOnDestroy() {
13776
+ this.asyncSub?.unsubscribe();
13777
+ this.querySubject.complete();
13778
+ }
13779
+ writeValue(value) {
13780
+ if (this.multiple()) {
13781
+ this.selectedMultiple.set(Array.isArray(value) ? value : []);
13782
+ }
13783
+ else {
13784
+ this.selectedSingle.set(value && !Array.isArray(value) ? value : null);
13785
+ if (value && !Array.isArray(value)) {
13786
+ this.query.set(value.label);
13787
+ }
13788
+ }
13789
+ }
13790
+ registerOnChange(fn) {
13791
+ this.onChange = fn;
13792
+ }
13793
+ registerOnTouched(fn) {
13794
+ this.onTouched = fn;
13795
+ }
13796
+ setDisabledState(isDisabled) {
13797
+ // handled by input
13798
+ }
13799
+ onInputChange(event) {
13800
+ const value = event.target.value;
13801
+ this.query.set(value);
13802
+ this.queryChange.emit(value);
13803
+ this.highlightedIndex.set(-1);
13804
+ if (this.loadOptions()) {
13805
+ this.querySubject.next(value);
13806
+ }
13807
+ if (value.length >= this.minChars()) {
13808
+ this.isOpen.set(true);
13809
+ }
13810
+ }
13811
+ onInputFocus() {
13812
+ if (this.query().length >= this.minChars() || this.options().length > 0) {
13813
+ this.isOpen.set(true);
13814
+ }
13815
+ }
13816
+ onInputBlur() {
13817
+ this.onTouched();
13818
+ // Delay to allow click on dropdown
13819
+ setTimeout(() => this.isOpen.set(false), 200);
13820
+ }
13821
+ onKeydown(event) {
13822
+ const flat = this.flatOptions();
13823
+ const total = flat.length + (this.showCreateOption() ? 1 : 0);
13824
+ switch (event.key) {
13825
+ case 'ArrowDown':
13826
+ event.preventDefault();
13827
+ this.isOpen.set(true);
13828
+ this.highlightedIndex.update((i) => (i + 1) % total);
13829
+ break;
13830
+ case 'ArrowUp':
13831
+ event.preventDefault();
13832
+ this.highlightedIndex.update((i) => (i - 1 + total) % total);
13833
+ break;
13834
+ case 'Enter':
13835
+ event.preventDefault();
13836
+ if (this.highlightedIndex() >= 0) {
13837
+ if (this.highlightedIndex() === flat.length && this.showCreateOption()) {
13838
+ this.onCreateNew();
13839
+ }
13840
+ else {
13841
+ const opt = flat[this.highlightedIndex()];
13842
+ if (opt && !opt.disabled) {
13843
+ this.selectOption(opt);
13844
+ }
13845
+ }
13846
+ }
13847
+ else if (this.showCreateOption()) {
13848
+ this.onCreateNew();
13849
+ }
13850
+ break;
13851
+ case 'Escape':
13852
+ this.isOpen.set(false);
13853
+ break;
13854
+ case 'Tab':
13855
+ if (this.highlightedIndex() >= 0 && this.isOpen()) {
13856
+ const opt = flat[this.highlightedIndex()];
13857
+ if (opt && !opt.disabled) {
13858
+ this.selectOption(opt);
13859
+ }
13860
+ }
13861
+ this.isOpen.set(false);
13862
+ break;
13863
+ case 'Backspace':
13864
+ if (this.multiple() && !this.query() && this.selectedMultiple().length > 0) {
13865
+ this.removeSelected(this.selectedMultiple()[this.selectedMultiple().length - 1]);
13866
+ }
13867
+ break;
13868
+ }
13869
+ }
13870
+ selectOption(option) {
13871
+ if (this.multiple()) {
13872
+ this.selectedMultiple.update((sel) => [...sel, option]);
13873
+ this.query.set('');
13874
+ const value = this.selectedMultiple();
13875
+ this.onChange(value);
13876
+ this.valueChange.emit(value);
13877
+ }
13878
+ else {
13879
+ this.selectedSingle.set(option);
13880
+ this.query.set(option.label);
13881
+ this.onChange(option);
13882
+ this.valueChange.emit(option);
13883
+ }
13884
+ this.optionSelected.emit(option);
13885
+ this.isOpen.set(false);
13886
+ this.highlightedIndex.set(-1);
13887
+ }
13888
+ removeSelected(option) {
13889
+ this.selectedMultiple.update((sel) => sel.filter((s) => s.value !== option.value));
13890
+ const value = this.selectedMultiple();
13891
+ this.onChange(value);
13892
+ this.valueChange.emit(value);
13893
+ this.inputEl?.nativeElement.focus();
13894
+ }
13895
+ onCreateNew() {
13896
+ const q = this.query().trim();
13897
+ if (!q)
13898
+ return;
13899
+ const newOption = { value: q, label: q };
13900
+ this.created.emit(q);
13901
+ this.selectOption(newOption);
13902
+ }
13903
+ onDocumentClick(event) {
13904
+ if (!this.elRef.nativeElement.contains(event.target)) {
13905
+ this.isOpen.set(false);
13906
+ }
13907
+ }
13908
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13909
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: ComboboxComponent, isStandalone: true, selector: "lc-combobox", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, loadOptions: { classPropertyName: "loadOptions", publicName: "loadOptions", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", queryChange: "queryChange", optionSelected: "optionSelected", created: "created" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, providers: [
13910
+ {
13911
+ provide: NG_VALUE_ACCESSOR,
13912
+ useExisting: forwardRef(() => ComboboxComponent),
13913
+ multi: true,
13914
+ },
13915
+ ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true }], ngImport: i0, template: "<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"lc-combobox__label\">{{ label() }}</label>\n }\n\n <div class=\"lc-combobox__input-wrapper\">\n <!-- Selected chips (multiple mode) -->\n @if (multiple()) {\n @for (sel of selectedMultiple(); track sel.value) {\n <lc-chip\n [removable]=\"!disabled()\"\n size=\"sm\"\n (remove)=\"removeSelected(sel)\"\n >{{ sel.label }}</lc-chip>\n }\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"lc-combobox__input\"\n [placeholder]=\"multiple() && selectedMultiple().length > 0 ? '' : placeholder()\"\n [value]=\"query()\"\n [disabled]=\"disabled()\"\n role=\"combobox\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n autocomplete=\"off\"\n (input)=\"onInputChange($event)\"\n (focus)=\"onInputFocus()\"\n (blur)=\"onInputBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (isLoading() || loading()) {\n <lc-spinner size=\"sm\" class=\"lc-combobox__spinner\" />\n } @else if (!multiple() && selectedSingle()) {\n <button\n type=\"button\"\n class=\"lc-combobox__clear\"\n (click)=\"selectOption({value: '', label: ''}); query.set('')\"\n tabindex=\"-1\"\n aria-label=\"Clear\"\n >\n <lc-icon name=\"x-mark\" size=\"xs\" />\n </button>\n }\n </div>\n\n <!-- Dropdown -->\n @if (isOpen()) {\n <div class=\"lc-combobox__dropdown\" role=\"listbox\">\n @for (group of groupedOptions(); track group.label) {\n @if (group.label) {\n <div class=\"lc-combobox__group-header\">{{ group.label }}</div>\n }\n @for (option of group.items; track option.value; let i = $index) {\n <div\n class=\"lc-combobox__option\"\n [class.lc-combobox__option--highlighted]=\"flatOptions().indexOf(option) === highlightedIndex()\"\n [class.lc-combobox__option--disabled]=\"option.disabled\"\n role=\"option\"\n [attr.aria-selected]=\"flatOptions().indexOf(option) === highlightedIndex()\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"!option.disabled && selectOption(option)\"\n (mouseenter)=\"highlightedIndex.set(flatOptions().indexOf(option))\"\n >\n @if (option.icon) {\n <lc-icon [name]=\"option.icon\" size=\"sm\" class=\"lc-combobox__option-icon\" />\n }\n <div class=\"lc-combobox__option-content\">\n <span class=\"lc-combobox__option-label\">{{ option.label }}</span>\n @if (option.description) {\n <span class=\"lc-combobox__option-desc\">{{ option.description }}</span>\n }\n </div>\n </div>\n }\n }\n\n @if (showCreateOption()) {\n <div\n class=\"lc-combobox__option lc-combobox__option--create\"\n [class.lc-combobox__option--highlighted]=\"highlightedIndex() === flatOptions().length\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onCreateNew()\"\n (mouseenter)=\"highlightedIndex.set(flatOptions().length)\"\n >\n <lc-icon name=\"plus\" size=\"sm\" />\n <span>Create \"{{ query() }}\"</span>\n </div>\n }\n\n @if (visibleOptions().length === 0 && !showCreateOption() && !isLoading() && !loading()) {\n <div class=\"lc-combobox__empty\">{{ emptyMessage() }}</div>\n }\n </div>\n }\n\n @if (helperText() && !error()) {\n <span class=\"lc-combobox__helper\">{{ helperText() }}</span>\n }\n @if (error()) {\n <span class=\"lc-combobox__error\">{{ error() }}</span>\n }\n</div>\n", styles: [".lc-combobox{display:flex;flex-direction:column;gap:.25rem;position:relative;font-family:var(--font-family-sans)}.lc-combobox__label{font-size:var(--font-size-sm, .875rem);font-weight:500;color:var(--color-text, #111827);margin-bottom:.125rem}.lc-combobox__input-wrapper{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;border:1px solid var(--color-neutral-300, #d1d5db);border-radius:var(--radius-md, .375rem);background-color:var(--color-background, #ffffff);padding:.25rem .5rem;transition:border-color .15s,box-shadow .15s;cursor:text}.lc-combobox__input-wrapper:focus-within{border-color:var(--color-primary-500, #3b82f6);box-shadow:0 0 0 3px #3b82f626}.lc-combobox--error .lc-combobox__input-wrapper{border-color:var(--color-error-default, #ef4444)}.lc-combobox--error .lc-combobox__input-wrapper:focus-within{box-shadow:0 0 0 3px #ef444426}.lc-combobox--disabled .lc-combobox__input-wrapper{opacity:.5;cursor:not-allowed;background-color:var(--color-neutral-50, #f9fafb)}.lc-combobox__input{flex:1;min-width:80px;border:none;outline:none;background:transparent;color:var(--color-text, #111827);font-family:inherit}.lc-combobox__input::placeholder{color:var(--color-neutral-400, #9ca3af)}.lc-combobox__input:disabled{cursor:not-allowed}.lc-combobox--xs .lc-combobox__input{font-size:var(--font-size-xs, .75rem);height:1.5rem}.lc-combobox--sm .lc-combobox__input{font-size:var(--font-size-sm, .875rem);height:1.75rem}.lc-combobox--md .lc-combobox__input{font-size:var(--font-size-base, 1rem);height:2rem}.lc-combobox--lg .lc-combobox__input{font-size:var(--font-size-lg, 1.125rem);height:2.25rem}.lc-combobox__spinner{flex-shrink:0}.lc-combobox__clear{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;cursor:pointer;color:var(--color-neutral-400, #9ca3af);border-radius:var(--radius-sm, .25rem);flex-shrink:0}.lc-combobox__clear:hover{color:var(--color-text, #111827);background-color:var(--color-neutral-100, #f3f4f6)}.lc-combobox__dropdown{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;max-height:240px;overflow-y:auto;background-color:var(--color-background, #ffffff);border:1px solid var(--color-neutral-200, #e5e7eb);border-radius:var(--radius-md, .375rem);box-shadow:var(--elevation-lg, 0 10px 15px -3px rgba(0, 0, 0, .1));z-index:50}.lc-combobox__group-header{padding:.375rem .75rem;font-size:var(--font-size-xs, .75rem);font-weight:600;color:var(--color-neutral-500, #6b7280);text-transform:uppercase;letter-spacing:.05em;margin-top:.25rem}.lc-combobox__group-header:first-child{margin-top:0}.lc-combobox__option{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;cursor:pointer;transition:background-color .1s}.lc-combobox__option--highlighted{background-color:var(--color-neutral-100, #f3f4f6)}.lc-combobox__option--disabled{opacity:.5;cursor:not-allowed}.lc-combobox__option--create{color:var(--color-primary-600, #2563eb);font-weight:500;border-top:1px solid var(--color-neutral-100, #f3f4f6)}.lc-combobox__option-icon{flex-shrink:0;color:var(--color-neutral-500, #6b7280)}.lc-combobox__option-content{display:flex;flex-direction:column;min-width:0}.lc-combobox__option-label{font-size:var(--font-size-sm, .875rem);color:var(--color-text, #111827);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-combobox__option-desc{font-size:var(--font-size-xs, .75rem);color:var(--color-text-secondary, #6b7280);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-combobox__empty{padding:.75rem;text-align:center;color:var(--color-neutral-400, #9ca3af);font-size:var(--font-size-sm, .875rem)}.lc-combobox__helper{font-size:var(--font-size-xs, .75rem);color:var(--color-text-secondary, #6b7280)}.lc-combobox__error{font-size:var(--font-size-xs, .75rem);color:var(--color-error-default, #ef4444)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "lc-icon", inputs: ["name", "variant", "size", "color", "ariaLabel", "decorative"] }, { kind: "component", type: SpinnerComponent, selector: "lc-spinner", inputs: ["size", "message"] }, { kind: "component", type: ChipComponent, selector: "lc-chip", inputs: ["variant", "size", "icon", "removable", "disabled"], outputs: ["remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13916
+ }
13917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ComboboxComponent, decorators: [{
13918
+ type: Component,
13919
+ args: [{ selector: 'lc-combobox', standalone: true, imports: [CommonModule, IconComponent, SpinnerComponent, ChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
13920
+ {
13921
+ provide: NG_VALUE_ACCESSOR,
13922
+ useExisting: forwardRef(() => ComboboxComponent),
13923
+ multi: true,
13924
+ },
13925
+ ], template: "<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"lc-combobox__label\">{{ label() }}</label>\n }\n\n <div class=\"lc-combobox__input-wrapper\">\n <!-- Selected chips (multiple mode) -->\n @if (multiple()) {\n @for (sel of selectedMultiple(); track sel.value) {\n <lc-chip\n [removable]=\"!disabled()\"\n size=\"sm\"\n (remove)=\"removeSelected(sel)\"\n >{{ sel.label }}</lc-chip>\n }\n }\n\n <input\n #inputEl\n type=\"text\"\n class=\"lc-combobox__input\"\n [placeholder]=\"multiple() && selectedMultiple().length > 0 ? '' : placeholder()\"\n [value]=\"query()\"\n [disabled]=\"disabled()\"\n role=\"combobox\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n autocomplete=\"off\"\n (input)=\"onInputChange($event)\"\n (focus)=\"onInputFocus()\"\n (blur)=\"onInputBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (isLoading() || loading()) {\n <lc-spinner size=\"sm\" class=\"lc-combobox__spinner\" />\n } @else if (!multiple() && selectedSingle()) {\n <button\n type=\"button\"\n class=\"lc-combobox__clear\"\n (click)=\"selectOption({value: '', label: ''}); query.set('')\"\n tabindex=\"-1\"\n aria-label=\"Clear\"\n >\n <lc-icon name=\"x-mark\" size=\"xs\" />\n </button>\n }\n </div>\n\n <!-- Dropdown -->\n @if (isOpen()) {\n <div class=\"lc-combobox__dropdown\" role=\"listbox\">\n @for (group of groupedOptions(); track group.label) {\n @if (group.label) {\n <div class=\"lc-combobox__group-header\">{{ group.label }}</div>\n }\n @for (option of group.items; track option.value; let i = $index) {\n <div\n class=\"lc-combobox__option\"\n [class.lc-combobox__option--highlighted]=\"flatOptions().indexOf(option) === highlightedIndex()\"\n [class.lc-combobox__option--disabled]=\"option.disabled\"\n role=\"option\"\n [attr.aria-selected]=\"flatOptions().indexOf(option) === highlightedIndex()\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"!option.disabled && selectOption(option)\"\n (mouseenter)=\"highlightedIndex.set(flatOptions().indexOf(option))\"\n >\n @if (option.icon) {\n <lc-icon [name]=\"option.icon\" size=\"sm\" class=\"lc-combobox__option-icon\" />\n }\n <div class=\"lc-combobox__option-content\">\n <span class=\"lc-combobox__option-label\">{{ option.label }}</span>\n @if (option.description) {\n <span class=\"lc-combobox__option-desc\">{{ option.description }}</span>\n }\n </div>\n </div>\n }\n }\n\n @if (showCreateOption()) {\n <div\n class=\"lc-combobox__option lc-combobox__option--create\"\n [class.lc-combobox__option--highlighted]=\"highlightedIndex() === flatOptions().length\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onCreateNew()\"\n (mouseenter)=\"highlightedIndex.set(flatOptions().length)\"\n >\n <lc-icon name=\"plus\" size=\"sm\" />\n <span>Create \"{{ query() }}\"</span>\n </div>\n }\n\n @if (visibleOptions().length === 0 && !showCreateOption() && !isLoading() && !loading()) {\n <div class=\"lc-combobox__empty\">{{ emptyMessage() }}</div>\n }\n </div>\n }\n\n @if (helperText() && !error()) {\n <span class=\"lc-combobox__helper\">{{ helperText() }}</span>\n }\n @if (error()) {\n <span class=\"lc-combobox__error\">{{ error() }}</span>\n }\n</div>\n", styles: [".lc-combobox{display:flex;flex-direction:column;gap:.25rem;position:relative;font-family:var(--font-family-sans)}.lc-combobox__label{font-size:var(--font-size-sm, .875rem);font-weight:500;color:var(--color-text, #111827);margin-bottom:.125rem}.lc-combobox__input-wrapper{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;border:1px solid var(--color-neutral-300, #d1d5db);border-radius:var(--radius-md, .375rem);background-color:var(--color-background, #ffffff);padding:.25rem .5rem;transition:border-color .15s,box-shadow .15s;cursor:text}.lc-combobox__input-wrapper:focus-within{border-color:var(--color-primary-500, #3b82f6);box-shadow:0 0 0 3px #3b82f626}.lc-combobox--error .lc-combobox__input-wrapper{border-color:var(--color-error-default, #ef4444)}.lc-combobox--error .lc-combobox__input-wrapper:focus-within{box-shadow:0 0 0 3px #ef444426}.lc-combobox--disabled .lc-combobox__input-wrapper{opacity:.5;cursor:not-allowed;background-color:var(--color-neutral-50, #f9fafb)}.lc-combobox__input{flex:1;min-width:80px;border:none;outline:none;background:transparent;color:var(--color-text, #111827);font-family:inherit}.lc-combobox__input::placeholder{color:var(--color-neutral-400, #9ca3af)}.lc-combobox__input:disabled{cursor:not-allowed}.lc-combobox--xs .lc-combobox__input{font-size:var(--font-size-xs, .75rem);height:1.5rem}.lc-combobox--sm .lc-combobox__input{font-size:var(--font-size-sm, .875rem);height:1.75rem}.lc-combobox--md .lc-combobox__input{font-size:var(--font-size-base, 1rem);height:2rem}.lc-combobox--lg .lc-combobox__input{font-size:var(--font-size-lg, 1.125rem);height:2.25rem}.lc-combobox__spinner{flex-shrink:0}.lc-combobox__clear{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;cursor:pointer;color:var(--color-neutral-400, #9ca3af);border-radius:var(--radius-sm, .25rem);flex-shrink:0}.lc-combobox__clear:hover{color:var(--color-text, #111827);background-color:var(--color-neutral-100, #f3f4f6)}.lc-combobox__dropdown{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;max-height:240px;overflow-y:auto;background-color:var(--color-background, #ffffff);border:1px solid var(--color-neutral-200, #e5e7eb);border-radius:var(--radius-md, .375rem);box-shadow:var(--elevation-lg, 0 10px 15px -3px rgba(0, 0, 0, .1));z-index:50}.lc-combobox__group-header{padding:.375rem .75rem;font-size:var(--font-size-xs, .75rem);font-weight:600;color:var(--color-neutral-500, #6b7280);text-transform:uppercase;letter-spacing:.05em;margin-top:.25rem}.lc-combobox__group-header:first-child{margin-top:0}.lc-combobox__option{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;cursor:pointer;transition:background-color .1s}.lc-combobox__option--highlighted{background-color:var(--color-neutral-100, #f3f4f6)}.lc-combobox__option--disabled{opacity:.5;cursor:not-allowed}.lc-combobox__option--create{color:var(--color-primary-600, #2563eb);font-weight:500;border-top:1px solid var(--color-neutral-100, #f3f4f6)}.lc-combobox__option-icon{flex-shrink:0;color:var(--color-neutral-500, #6b7280)}.lc-combobox__option-content{display:flex;flex-direction:column;min-width:0}.lc-combobox__option-label{font-size:var(--font-size-sm, .875rem);color:var(--color-text, #111827);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-combobox__option-desc{font-size:var(--font-size-xs, .75rem);color:var(--color-text-secondary, #6b7280);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-combobox__empty{padding:.75rem;text-align:center;color:var(--color-neutral-400, #9ca3af);font-size:var(--font-size-sm, .875rem)}.lc-combobox__helper{font-size:var(--font-size-xs, .75rem);color:var(--color-text-secondary, #6b7280)}.lc-combobox__error{font-size:var(--font-size-xs, .75rem);color:var(--color-error-default, #ef4444)}\n"] }]
13926
+ }], ctorParameters: () => [], propDecorators: { inputEl: [{
13927
+ type: ViewChild,
13928
+ args: ['inputEl']
13929
+ }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], loadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadOptions", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], queryChange: [{ type: i0.Output, args: ["queryChange"] }], optionSelected: [{ type: i0.Output, args: ["optionSelected"] }], created: [{ type: i0.Output, args: ["created"] }], onDocumentClick: [{
13930
+ type: HostListener,
13931
+ args: ['document:click', ['$event']]
13932
+ }] } });
13933
+
12851
13934
  /**
12852
13935
  * Life-Cockpit Design System (UI Kit)
12853
13936
  *
@@ -12870,5 +13953,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
12870
13953
  * Generated bundle index. Do not edit.
12871
13954
  */
12872
13955
 
12873
- export { AccordionComponent, AlertComponent, AnimationDurationFast, AnimationEasingEaseIn, AnimationEasingEaseInOut, AnimationEasingEaseOut, AreaChartComponent, AvatarComponent, AvatarGroupComponent, BadgeComponent, BarChartComponent, BorderRadius2xl, BorderRadiusFull, BorderRadiusLg, BorderRadiusMd, BorderRadiusNone, BorderRadiusSm, BorderRadiusXl, BreadcrumbsComponent, ButtonComponent, CalendarComponent, CalloutComponent, CardComponent, ChatComponent, CheckboxComponent, ChipComponent, CodeBlockComponent, ColorAccentOrange, ColorAccentPurple, ColorAccentRed, ColorAccentRust, ColorAccentViolet, ColorErrorDark, ColorErrorDefault, ColorErrorLight, ColorInfoDark, ColorInfoDefault, ColorInfoLight, ColorNeutral100, ColorNeutral200, ColorNeutral300, ColorNeutral400, ColorNeutral50, ColorNeutral500, ColorNeutral600, ColorNeutral700, ColorNeutral800, ColorNeutral900, ColorPickerComponent, ColorPrimary100, ColorPrimary200, ColorPrimary300, ColorPrimary400, ColorPrimary50, ColorPrimary500, ColorPrimary600, ColorPrimary700, ColorPrimary800, ColorPrimary900, ColorSecondary100, ColorSecondary200, ColorSecondary300, ColorSecondary400, ColorSecondary50, ColorSecondary500, ColorSecondary600, ColorSecondary700, ColorSecondary800, ColorSecondary900, ColorSuccessDark, ColorSuccessDefault, ColorSuccessLight, ColorWarningDark, ColorWarningDefault, ColorWarningLight, ContainerComponent, DateRangePickerComponent, DatepickerComponent, DependencyViewerComponent, DiffViewerComponent, DividerComponent, DocumentViewerComponent, DonutChartComponent, DrawerComponent, Elevation1, Elevation2, Elevation3, Elevation4, EmailInputComponent, EmptyStateComponent, ErrorDisplayComponent, FieldGroupComponent, FileUploadComponent, FilterBarComponent, FooterComponent, FunnelChartComponent, GalleryComponent, GanttChartComponent, GaugeComponent, HeaderComponent, HeatmapComponent, HeroComponent, IconComponent, InputComponent, KanbanBoardComponent, LineChartComponent, ListComponent, ListItemTemplateDirective, LogoComponent, MenuComponent, ModalComponent, NotificationCenterComponent, NumberInputComponent, PaginationComponent, PasswordInputComponent, PieChartComponent, PopoverComponent, ProgressBarComponent, ProgressRingComponent, RadarChartComponent, RadioComponent, RatingComponent, RichTextEditorComponent, ScatterPlotComponent, SearchInputComponent, SectionComponent, SelectComponent, SidenavComponent, SizeInteractiveLgFontSize, SizeInteractiveLgHeight, SizeInteractiveLgPadding, SizeInteractiveMdFontSize, SizeInteractiveMdHeight, SizeInteractiveMdPadding, SizeInteractiveSmFontSize, SizeInteractiveSmHeight, SizeInteractiveSmPadding, SizeInteractiveXsFontSize, SizeInteractiveXsHeight, SizeInteractiveXsPadding, SizeMinTouchHeight, SizeMinTouchWidth, SkeletonComponent, SliderComponent, SpacerComponent, Spacing0, Spacing05, Spacing1, Spacing10, Spacing11, Spacing12, Spacing14, Spacing15, Spacing16, Spacing2, Spacing25, Spacing3, Spacing35, Spacing4, Spacing5, Spacing6, Spacing7, Spacing8, Spacing9, SparklineComponent, SpinnerComponent, StackComponent, StackedBarChartComponent, StatTrendComponent, StepperComponent, SwitchComponent, TabComponent, TableCellDirective, TableComponent, TabsComponent, TagInputComponent, TextareaComponent, ThemeService, TimelineComponent, ToastComponent, ToastService, ToggleGroupComponent, TooltipContentComponent, TooltipDirective, TypographyComponent, TypographyFontFamilyBase, TypographyFontFamilyMono, TypographyFontSize2xl, TypographyFontSize3xl, TypographyFontSize4xl, TypographyFontSize5xl, TypographyFontSize6xl, TypographyFontSizeBase, TypographyFontSizeLg, TypographyFontSizeSm, TypographyFontSizeXl, TypographyFontSizeXs, TypographyFontWeightBold, TypographyFontWeightMedium, TypographyFontWeightNormal, TypographyFontWeightSemibold, TypographyLineHeightNormal, TypographyLineHeightRelaxed, TypographyLineHeightTight, VerificationCodeInputComponent, WaterfallChartComponent };
13956
+ export { AccordionComponent, AlertComponent, AnimationDurationFast, AnimationEasingEaseIn, AnimationEasingEaseInOut, AnimationEasingEaseOut, AreaChartComponent, AvatarComponent, AvatarGroupComponent, BadgeComponent, BarChartComponent, BorderRadius2xl, BorderRadiusFull, BorderRadiusLg, BorderRadiusMd, BorderRadiusNone, BorderRadiusSm, BorderRadiusXl, BreadcrumbsComponent, ButtonComponent, CalendarComponent, CalloutComponent, CardComponent, ChatComponent, CheckboxComponent, ChipComponent, CodeBlockComponent, ColorAccentOrange, ColorAccentPurple, ColorAccentRed, ColorAccentRust, ColorAccentViolet, ColorErrorDark, ColorErrorDefault, ColorErrorLight, ColorInfoDark, ColorInfoDefault, ColorInfoLight, ColorNeutral100, ColorNeutral200, ColorNeutral300, ColorNeutral400, ColorNeutral50, ColorNeutral500, ColorNeutral600, ColorNeutral700, ColorNeutral800, ColorNeutral900, ColorPickerComponent, ColorPrimary100, ColorPrimary200, ColorPrimary300, ColorPrimary400, ColorPrimary50, ColorPrimary500, ColorPrimary600, ColorPrimary700, ColorPrimary800, ColorPrimary900, ColorSecondary100, ColorSecondary200, ColorSecondary300, ColorSecondary400, ColorSecondary50, ColorSecondary500, ColorSecondary600, ColorSecondary700, ColorSecondary800, ColorSecondary900, ColorSuccessDark, ColorSuccessDefault, ColorSuccessLight, ColorWarningDark, ColorWarningDefault, ColorWarningLight, ComboboxComponent, ConfirmDialogComponent, ConfirmService, ContainerComponent, DateRangePickerComponent, DatepickerComponent, DependencyViewerComponent, DiffViewerComponent, DividerComponent, DocumentViewerComponent, DonutChartComponent, DrawerComponent, Elevation1, Elevation2, Elevation3, Elevation4, EmailInputComponent, EmptyStateComponent, ErrorDisplayComponent, FieldGroupComponent, FileUploadComponent, FilterBarComponent, FooterComponent, FunnelChartComponent, GalleryComponent, GanttChartComponent, GaugeComponent, HeaderComponent, HeatmapComponent, HeroComponent, IconComponent, InputComponent, KanbanBoardComponent, LineChartComponent, ListComponent, ListItemTemplateDirective, LogViewerComponent, LogoComponent, MarkdownComponent, MenuComponent, ModalComponent, NotificationCenterComponent, NumberInputComponent, PaginationComponent, PasswordInputComponent, PieChartComponent, PopoverComponent, ProgressBarComponent, ProgressRingComponent, RadarChartComponent, RadioComponent, RatingComponent, RichTextEditorComponent, ScatterPlotComponent, SearchInputComponent, SectionComponent, SelectComponent, SidenavComponent, SizeInteractiveLgFontSize, SizeInteractiveLgHeight, SizeInteractiveLgPadding, SizeInteractiveMdFontSize, SizeInteractiveMdHeight, SizeInteractiveMdPadding, SizeInteractiveSmFontSize, SizeInteractiveSmHeight, SizeInteractiveSmPadding, SizeInteractiveXsFontSize, SizeInteractiveXsHeight, SizeInteractiveXsPadding, SizeMinTouchHeight, SizeMinTouchWidth, SkeletonComponent, SliderComponent, SpacerComponent, Spacing0, Spacing05, Spacing1, Spacing10, Spacing11, Spacing12, Spacing14, Spacing15, Spacing16, Spacing2, Spacing25, Spacing3, Spacing35, Spacing4, Spacing5, Spacing6, Spacing7, Spacing8, Spacing9, SparklineComponent, SpinnerComponent, StackComponent, StackedBarChartComponent, StatTrendComponent, StepperComponent, SwitchComponent, TabComponent, TableCellDirective, TableComponent, TabsComponent, TagInputComponent, TextareaComponent, ThemeService, TimelineComponent, ToastComponent, ToastService, ToggleGroupComponent, TooltipContentComponent, TooltipDirective, TypographyComponent, TypographyFontFamilyBase, TypographyFontFamilyMono, TypographyFontSize2xl, TypographyFontSize3xl, TypographyFontSize4xl, TypographyFontSize5xl, TypographyFontSize6xl, TypographyFontSizeBase, TypographyFontSizeLg, TypographyFontSizeSm, TypographyFontSizeXl, TypographyFontSizeXs, TypographyFontWeightBold, TypographyFontWeightMedium, TypographyFontWeightNormal, TypographyFontWeightSemibold, TypographyLineHeightNormal, TypographyLineHeightRelaxed, TypographyLineHeightTight, VerificationCodeInputComponent, WaterfallChartComponent };
12874
13957
  //# sourceMappingURL=life-cockpit-angular-ui-kit.mjs.map