@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 <!--CODE_BLOCK_N-->
|
|
12961
|
+
const regex = /<!--CODE_BLOCK_(\d+)-->/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, '&')
|
|
13014
|
+
.replace(/</g, '<')
|
|
13015
|
+
.replace(/>/g, '>');
|
|
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(/^(?:>)\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(/<!--CODE_BLOCK_(\d+)-->/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, '&')
|
|
13129
|
+
.replace(/</g, '<')
|
|
13130
|
+
.replace(/>/g, '>');
|
|
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, '&')
|
|
13409
|
+
.replace(/</g, '<')
|
|
13410
|
+
.replace(/>/g, '>');
|
|
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
|