@life-cockpit/angular-ui-kit 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- 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
- import { isPlatformBrowser, CommonModule, SlicePipe, NgStyle } from '@angular/common';
4
+ import { isPlatformBrowser, CommonModule, SlicePipe, NgTemplateOutlet, NgStyle } from '@angular/common';
5
5
  import * as i1$4 from '@angular/platform-browser';
6
6
  import { DomSanitizer } from '@angular/platform-browser';
7
7
  import { HttpClient } from '@angular/common/http';
@@ -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';
@@ -10507,6 +10507,8 @@ class ChatComponent {
10507
10507
  showTimestamps = input(true, ...(ngDevMode ? [{ debugName: "showTimestamps" }] : /* istanbul ignore next */ []));
10508
10508
  /** Emits when user sends a message. */
10509
10509
  messageSend = output();
10510
+ /** Custom template for rendering message content. Context: { $implicit: ChatMessage } */
10511
+ messageTemplate;
10510
10512
  inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
10511
10513
  shouldScroll = false;
10512
10514
  scrollContainer;
@@ -10552,12 +10554,15 @@ class ChatComponent {
10552
10554
  }
10553
10555
  }
10554
10556
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10555
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: ChatComponent, isStandalone: true, selector: "lc-chat", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, isStreaming: { classPropertyName: "isStreaming", publicName: "isStreaming", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showAvatars: { classPropertyName: "showAvatars", publicName: "showAvatars", isSignal: true, isRequired: false, transformFunction: null }, showTimestamps: { classPropertyName: "showTimestamps", publicName: "showTimestamps", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { messageSend: "messageSend" }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"lc-chat\">\n @if (showHeader()) {\n <div class=\"lc-chat__header\">\n <span class=\"lc-chat__title\">{{ title() }}</span>\n @if (isStreaming()) {\n <span class=\"lc-chat__streaming-badge\">Streaming\u2026</span>\n }\n </div>\n }\n\n <div class=\"lc-chat__messages\" #scrollContainer>\n @for (msg of formattedMessages(); track trackById($index, msg)) {\n <div\n class=\"lc-chat__message\"\n [class.lc-chat__message--user]=\"msg.role === 'user'\"\n [class.lc-chat__message--agent]=\"msg.role === 'agent'\"\n [class.lc-chat__message--system]=\"msg.role === 'system'\"\n >\n @if (showAvatars() && msg.role !== 'system') {\n <div class=\"lc-chat__avatar\">\n @if (msg.avatar) {\n <img [src]=\"msg.avatar\" [alt]=\"msg.name || msg.role\" class=\"lc-chat__avatar-img\" />\n } @else {\n <span class=\"lc-chat__avatar-fallback\" [class.lc-chat__avatar-fallback--agent]=\"msg.role === 'agent'\">\n {{ (msg.name || msg.role).charAt(0).toUpperCase() }}\n </span>\n }\n </div>\n }\n\n <div class=\"lc-chat__bubble\" [class.lc-chat__bubble--streaming]=\"msg.streaming\">\n @if (msg.name && msg.role !== 'user') {\n <div class=\"lc-chat__name\">{{ msg.name }}</div>\n }\n <div class=\"lc-chat__content\">{{ msg.content }}<span class=\"lc-chat__cursor\" [class.lc-chat__cursor--visible]=\"msg.streaming\"></span></div>\n @if (showTimestamps() && msg.timestamp) {\n <div class=\"lc-chat__time\">{{ formatTime(msg.timestamp) }}</div>\n }\n </div>\n </div>\n }\n\n @if (isStreaming() && !(formattedMessages().length && formattedMessages()[formattedMessages().length - 1].streaming)) {\n <div class=\"lc-chat__message lc-chat__message--agent\">\n @if (showAvatars()) {\n <div class=\"lc-chat__avatar\">\n <span class=\"lc-chat__avatar-fallback lc-chat__avatar-fallback--agent\">A</span>\n </div>\n }\n <div class=\"lc-chat__bubble\">\n <div class=\"lc-chat__typing\">\n <span></span><span></span><span></span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"lc-chat__input-area\">\n <textarea\n class=\"lc-chat__input\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputValue()\"\n [disabled]=\"disabled()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n rows=\"1\"\n ></textarea>\n <button\n class=\"lc-chat__send-btn\"\n [disabled]=\"!inputValue().trim() || disabled()\"\n (click)=\"send()\"\n aria-label=\"Send\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M3 10l7-7m0 0l7 7m-7-7v14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" transform=\"rotate(90 10 10)\"/>\n </svg>\n </button>\n </div>\n</div>\n", styles: [".lc-chat{display:flex;flex-direction:column;height:100%;min-height:20rem;border:1px solid var(--color-neutral-200);border-radius:.5rem;overflow:hidden;background:var(--color-neutral-0, #fff);font-family:var(--typography-font-family, sans-serif)}[data-theme=dark] .lc-chat,:root[data-theme=dark] .lc-chat{border-color:var(--color-neutral-700);background:var(--color-neutral-900)}.lc-chat__header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__header,:root[data-theme=dark] .lc-chat__header{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__title{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}[data-theme=dark] .lc-chat__title,:root[data-theme=dark] .lc-chat__title{color:var(--color-neutral-100)}.lc-chat__streaming-badge{font-size:.6875rem;padding:.125rem .5rem;background:color-mix(in srgb,var(--color-primary-500) 15%,transparent);color:var(--color-primary-500);border-radius:1rem;font-weight:500;animation:pulse-badge 1.5s infinite}@keyframes pulse-badge{0%,to{opacity:1}50%{opacity:.6}}.lc-chat__messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem}.lc-chat__message{display:flex;align-items:flex-start;gap:.5rem;max-width:85%}.lc-chat__message--user{align-self:flex-end;flex-direction:row-reverse}.lc-chat__message--agent{align-self:flex-start}.lc-chat__message--system{align-self:center;max-width:100%}.lc-chat__avatar{flex-shrink:0;width:2rem;height:2rem}.lc-chat__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.lc-chat__avatar-fallback{display:flex;align-items:center;justify-content:center;width:100%;height:100%;border-radius:50%;background:var(--color-neutral-300);color:#fff;font-size:.75rem;font-weight:700}.lc-chat__avatar-fallback--agent{background:var(--color-primary-500)}.lc-chat__bubble{padding:.5rem .75rem;border-radius:1rem;font-size:.875rem;line-height:1.5;word-break:break-word}.lc-chat__message--user .lc-chat__bubble{background:var(--color-primary-500);color:#fff;border-bottom-right-radius:.25rem}.lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-100);color:var(--color-neutral-900);border-bottom-left-radius:.25rem}[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble,:root[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-800);color:var(--color-neutral-100)}.lc-chat__message--system .lc-chat__bubble{background:transparent;color:var(--color-neutral-500);font-size:.75rem;text-align:center;padding:.25rem .5rem}.lc-chat__message--agent .lc-chat__bubble--streaming{border:1px solid color-mix(in srgb,var(--color-primary-500) 30%,transparent)}.lc-chat__name{font-size:.6875rem;font-weight:600;color:var(--color-primary-500);margin-bottom:.125rem}.lc-chat__content{white-space:pre-wrap}.lc-chat__cursor{display:none}.lc-chat__cursor--visible{display:inline-block;width:2px;height:1em;background:currentColor;margin-left:1px;vertical-align:text-bottom;animation:blink-cursor .8s step-end infinite}@keyframes blink-cursor{0%,to{opacity:1}50%{opacity:0}}.lc-chat__time{font-size:.625rem;color:var(--color-neutral-400);margin-top:.25rem}.lc-chat__message--user .lc-chat__time{color:#ffffffb3;text-align:right}.lc-chat__typing{display:flex;gap:.25rem;padding:.25rem 0}.lc-chat__typing span{width:.375rem;height:.375rem;border-radius:50%;background:var(--color-neutral-400);animation:typing-bounce 1.4s ease-in-out infinite}.lc-chat__typing span:nth-child(2){animation-delay:.2s}.lc-chat__typing span:nth-child(3){animation-delay:.4s}@keyframes typing-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-.375rem)}}.lc-chat__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__input-area,:root[data-theme=dark] .lc-chat__input-area{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__input{flex:1;resize:none;border:1px solid var(--color-neutral-300);border-radius:1.25rem;padding:.5rem .875rem;font-size:.875rem;font-family:inherit;line-height:1.4;outline:none;background:var(--color-neutral-0, #fff);color:var(--color-neutral-900);max-height:6rem;overflow-y:auto;transition:border-color .15s}.lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__input:disabled{opacity:.5;cursor:not-allowed}.lc-chat__input::placeholder{color:var(--color-neutral-400)}[data-theme=dark] .lc-chat__input,:root[data-theme=dark] .lc-chat__input{border-color:var(--color-neutral-600);background:var(--color-neutral-700);color:var(--color-neutral-100)}[data-theme=dark] .lc-chat__input::placeholder,:root[data-theme=dark] .lc-chat__input::placeholder{color:var(--color-neutral-500)}[data-theme=dark] .lc-chat__input:focus,:root[data-theme=dark] .lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__send-btn{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:none;border-radius:50%;background:var(--color-primary-500);color:#fff;cursor:pointer;flex-shrink:0;transition:background .15s,opacity .15s}.lc-chat__send-btn:hover:not(:disabled){background:var(--color-primary-700)}.lc-chat__send-btn:disabled{opacity:.4;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10557
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: ChatComponent, isStandalone: true, selector: "lc-chat", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, isStreaming: { classPropertyName: "isStreaming", publicName: "isStreaming", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showAvatars: { classPropertyName: "showAvatars", publicName: "showAvatars", isSignal: true, isRequired: false, transformFunction: null }, showTimestamps: { classPropertyName: "showTimestamps", publicName: "showTimestamps", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { messageSend: "messageSend" }, queries: [{ propertyName: "messageTemplate", first: true, predicate: ["messageTemplate"], descendants: true }], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"lc-chat\">\n @if (showHeader()) {\n <div class=\"lc-chat__header\">\n <span class=\"lc-chat__title\">{{ title() }}</span>\n @if (isStreaming()) {\n <span class=\"lc-chat__streaming-badge\">Streaming\u2026</span>\n }\n </div>\n }\n\n <div class=\"lc-chat__messages\" #scrollContainer>\n @for (msg of formattedMessages(); track trackById($index, msg)) {\n <div\n class=\"lc-chat__message\"\n [class.lc-chat__message--user]=\"msg.role === 'user'\"\n [class.lc-chat__message--agent]=\"msg.role === 'agent'\"\n [class.lc-chat__message--system]=\"msg.role === 'system'\"\n >\n @if (showAvatars() && msg.role !== 'system') {\n <div class=\"lc-chat__avatar\">\n @if (msg.avatar) {\n <img [src]=\"msg.avatar\" [alt]=\"msg.name || msg.role\" class=\"lc-chat__avatar-img\" />\n } @else {\n <span class=\"lc-chat__avatar-fallback\" [class.lc-chat__avatar-fallback--agent]=\"msg.role === 'agent'\">\n {{ (msg.name || msg.role).charAt(0).toUpperCase() }}\n </span>\n }\n </div>\n }\n\n <div class=\"lc-chat__bubble\" [class.lc-chat__bubble--streaming]=\"msg.streaming\">\n @if (msg.name && msg.role !== 'user') {\n <div class=\"lc-chat__name\">{{ msg.name }}</div>\n }\n <div class=\"lc-chat__content\">\n @if (messageTemplate) {\n <ng-container *ngTemplateOutlet=\"messageTemplate; context: { $implicit: msg }\" />\n } @else {\n {{ msg.content }}\n }\n <span class=\"lc-chat__cursor\" [class.lc-chat__cursor--visible]=\"msg.streaming\"></span>\n </div>\n @if (showTimestamps() && msg.timestamp) {\n <div class=\"lc-chat__time\">{{ formatTime(msg.timestamp) }}</div>\n }\n </div>\n </div>\n }\n\n @if (isStreaming() && !(formattedMessages().length && formattedMessages()[formattedMessages().length - 1].streaming)) {\n <div class=\"lc-chat__message lc-chat__message--agent\">\n @if (showAvatars()) {\n <div class=\"lc-chat__avatar\">\n <span class=\"lc-chat__avatar-fallback lc-chat__avatar-fallback--agent\">A</span>\n </div>\n }\n <div class=\"lc-chat__bubble\">\n <div class=\"lc-chat__typing\">\n <span></span><span></span><span></span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"lc-chat__input-area\">\n <textarea\n class=\"lc-chat__input\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputValue()\"\n [disabled]=\"disabled()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n rows=\"1\"\n ></textarea>\n <button\n class=\"lc-chat__send-btn\"\n [disabled]=\"!inputValue().trim() || disabled()\"\n (click)=\"send()\"\n aria-label=\"Send\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M3 10l7-7m0 0l7 7m-7-7v14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" transform=\"rotate(90 10 10)\"/>\n </svg>\n </button>\n </div>\n</div>\n", styles: [".lc-chat{display:flex;flex-direction:column;height:100%;min-height:20rem;border:1px solid var(--color-neutral-200);border-radius:.5rem;overflow:hidden;background:var(--color-neutral-0, #fff);font-family:var(--typography-font-family, sans-serif)}[data-theme=dark] .lc-chat,:root[data-theme=dark] .lc-chat{border-color:var(--color-neutral-700);background:var(--color-neutral-900)}.lc-chat__header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__header,:root[data-theme=dark] .lc-chat__header{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__title{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}[data-theme=dark] .lc-chat__title,:root[data-theme=dark] .lc-chat__title{color:var(--color-neutral-100)}.lc-chat__streaming-badge{font-size:.6875rem;padding:.125rem .5rem;background:color-mix(in srgb,var(--color-primary-500) 15%,transparent);color:var(--color-primary-500);border-radius:1rem;font-weight:500;animation:pulse-badge 1.5s infinite}@keyframes pulse-badge{0%,to{opacity:1}50%{opacity:.6}}.lc-chat__messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem}.lc-chat__message{display:flex;align-items:flex-start;gap:.5rem;max-width:85%}.lc-chat__message--user{align-self:flex-end;flex-direction:row-reverse}.lc-chat__message--agent{align-self:flex-start}.lc-chat__message--system{align-self:center;max-width:100%}.lc-chat__avatar{flex-shrink:0;width:2rem;height:2rem}.lc-chat__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.lc-chat__avatar-fallback{display:flex;align-items:center;justify-content:center;width:100%;height:100%;border-radius:50%;background:var(--color-neutral-300);color:#fff;font-size:.75rem;font-weight:700}.lc-chat__avatar-fallback--agent{background:var(--color-primary-500)}.lc-chat__bubble{padding:.5rem .75rem;border-radius:1rem;font-size:.875rem;line-height:1.5;word-break:break-word}.lc-chat__message--user .lc-chat__bubble{background:var(--color-primary-500);color:#fff;border-bottom-right-radius:.25rem}.lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-100);color:var(--color-neutral-900);border-bottom-left-radius:.25rem}[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble,:root[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-800);color:var(--color-neutral-100)}.lc-chat__message--system .lc-chat__bubble{background:transparent;color:var(--color-neutral-500);font-size:.75rem;text-align:center;padding:.25rem .5rem}.lc-chat__message--agent .lc-chat__bubble--streaming{border:1px solid color-mix(in srgb,var(--color-primary-500) 30%,transparent)}.lc-chat__name{font-size:.6875rem;font-weight:600;color:var(--color-primary-500);margin-bottom:.125rem}.lc-chat__content{white-space:pre-wrap}.lc-chat__cursor{display:none}.lc-chat__cursor--visible{display:inline-block;width:2px;height:1em;background:currentColor;margin-left:1px;vertical-align:text-bottom;animation:blink-cursor .8s step-end infinite}@keyframes blink-cursor{0%,to{opacity:1}50%{opacity:0}}.lc-chat__time{font-size:.625rem;color:var(--color-neutral-400);margin-top:.25rem}.lc-chat__message--user .lc-chat__time{color:#ffffffb3;text-align:right}.lc-chat__typing{display:flex;gap:.25rem;padding:.25rem 0}.lc-chat__typing span{width:.375rem;height:.375rem;border-radius:50%;background:var(--color-neutral-400);animation:typing-bounce 1.4s ease-in-out infinite}.lc-chat__typing span:nth-child(2){animation-delay:.2s}.lc-chat__typing span:nth-child(3){animation-delay:.4s}@keyframes typing-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-.375rem)}}.lc-chat__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__input-area,:root[data-theme=dark] .lc-chat__input-area{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__input{flex:1;resize:none;border:1px solid var(--color-neutral-300);border-radius:1.25rem;padding:.5rem .875rem;font-size:.875rem;font-family:inherit;line-height:1.4;outline:none;background:var(--color-neutral-0, #fff);color:var(--color-neutral-900);max-height:6rem;overflow-y:auto;transition:border-color .15s}.lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__input:disabled{opacity:.5;cursor:not-allowed}.lc-chat__input::placeholder{color:var(--color-neutral-400)}[data-theme=dark] .lc-chat__input,:root[data-theme=dark] .lc-chat__input{border-color:var(--color-neutral-600);background:var(--color-neutral-700);color:var(--color-neutral-100)}[data-theme=dark] .lc-chat__input::placeholder,:root[data-theme=dark] .lc-chat__input::placeholder{color:var(--color-neutral-500)}[data-theme=dark] .lc-chat__input:focus,:root[data-theme=dark] .lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__send-btn{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:none;border-radius:50%;background:var(--color-primary-500);color:#fff;cursor:pointer;flex-shrink:0;transition:background .15s,opacity .15s}.lc-chat__send-btn:hover:not(:disabled){background:var(--color-primary-700)}.lc-chat__send-btn:disabled{opacity:.4;cursor:not-allowed}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10556
10558
  }
10557
10559
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ChatComponent, decorators: [{
10558
10560
  type: Component,
10559
- args: [{ selector: 'lc-chat', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"lc-chat\">\n @if (showHeader()) {\n <div class=\"lc-chat__header\">\n <span class=\"lc-chat__title\">{{ title() }}</span>\n @if (isStreaming()) {\n <span class=\"lc-chat__streaming-badge\">Streaming\u2026</span>\n }\n </div>\n }\n\n <div class=\"lc-chat__messages\" #scrollContainer>\n @for (msg of formattedMessages(); track trackById($index, msg)) {\n <div\n class=\"lc-chat__message\"\n [class.lc-chat__message--user]=\"msg.role === 'user'\"\n [class.lc-chat__message--agent]=\"msg.role === 'agent'\"\n [class.lc-chat__message--system]=\"msg.role === 'system'\"\n >\n @if (showAvatars() && msg.role !== 'system') {\n <div class=\"lc-chat__avatar\">\n @if (msg.avatar) {\n <img [src]=\"msg.avatar\" [alt]=\"msg.name || msg.role\" class=\"lc-chat__avatar-img\" />\n } @else {\n <span class=\"lc-chat__avatar-fallback\" [class.lc-chat__avatar-fallback--agent]=\"msg.role === 'agent'\">\n {{ (msg.name || msg.role).charAt(0).toUpperCase() }}\n </span>\n }\n </div>\n }\n\n <div class=\"lc-chat__bubble\" [class.lc-chat__bubble--streaming]=\"msg.streaming\">\n @if (msg.name && msg.role !== 'user') {\n <div class=\"lc-chat__name\">{{ msg.name }}</div>\n }\n <div class=\"lc-chat__content\">{{ msg.content }}<span class=\"lc-chat__cursor\" [class.lc-chat__cursor--visible]=\"msg.streaming\"></span></div>\n @if (showTimestamps() && msg.timestamp) {\n <div class=\"lc-chat__time\">{{ formatTime(msg.timestamp) }}</div>\n }\n </div>\n </div>\n }\n\n @if (isStreaming() && !(formattedMessages().length && formattedMessages()[formattedMessages().length - 1].streaming)) {\n <div class=\"lc-chat__message lc-chat__message--agent\">\n @if (showAvatars()) {\n <div class=\"lc-chat__avatar\">\n <span class=\"lc-chat__avatar-fallback lc-chat__avatar-fallback--agent\">A</span>\n </div>\n }\n <div class=\"lc-chat__bubble\">\n <div class=\"lc-chat__typing\">\n <span></span><span></span><span></span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"lc-chat__input-area\">\n <textarea\n class=\"lc-chat__input\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputValue()\"\n [disabled]=\"disabled()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n rows=\"1\"\n ></textarea>\n <button\n class=\"lc-chat__send-btn\"\n [disabled]=\"!inputValue().trim() || disabled()\"\n (click)=\"send()\"\n aria-label=\"Send\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M3 10l7-7m0 0l7 7m-7-7v14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" transform=\"rotate(90 10 10)\"/>\n </svg>\n </button>\n </div>\n</div>\n", styles: [".lc-chat{display:flex;flex-direction:column;height:100%;min-height:20rem;border:1px solid var(--color-neutral-200);border-radius:.5rem;overflow:hidden;background:var(--color-neutral-0, #fff);font-family:var(--typography-font-family, sans-serif)}[data-theme=dark] .lc-chat,:root[data-theme=dark] .lc-chat{border-color:var(--color-neutral-700);background:var(--color-neutral-900)}.lc-chat__header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__header,:root[data-theme=dark] .lc-chat__header{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__title{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}[data-theme=dark] .lc-chat__title,:root[data-theme=dark] .lc-chat__title{color:var(--color-neutral-100)}.lc-chat__streaming-badge{font-size:.6875rem;padding:.125rem .5rem;background:color-mix(in srgb,var(--color-primary-500) 15%,transparent);color:var(--color-primary-500);border-radius:1rem;font-weight:500;animation:pulse-badge 1.5s infinite}@keyframes pulse-badge{0%,to{opacity:1}50%{opacity:.6}}.lc-chat__messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem}.lc-chat__message{display:flex;align-items:flex-start;gap:.5rem;max-width:85%}.lc-chat__message--user{align-self:flex-end;flex-direction:row-reverse}.lc-chat__message--agent{align-self:flex-start}.lc-chat__message--system{align-self:center;max-width:100%}.lc-chat__avatar{flex-shrink:0;width:2rem;height:2rem}.lc-chat__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.lc-chat__avatar-fallback{display:flex;align-items:center;justify-content:center;width:100%;height:100%;border-radius:50%;background:var(--color-neutral-300);color:#fff;font-size:.75rem;font-weight:700}.lc-chat__avatar-fallback--agent{background:var(--color-primary-500)}.lc-chat__bubble{padding:.5rem .75rem;border-radius:1rem;font-size:.875rem;line-height:1.5;word-break:break-word}.lc-chat__message--user .lc-chat__bubble{background:var(--color-primary-500);color:#fff;border-bottom-right-radius:.25rem}.lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-100);color:var(--color-neutral-900);border-bottom-left-radius:.25rem}[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble,:root[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-800);color:var(--color-neutral-100)}.lc-chat__message--system .lc-chat__bubble{background:transparent;color:var(--color-neutral-500);font-size:.75rem;text-align:center;padding:.25rem .5rem}.lc-chat__message--agent .lc-chat__bubble--streaming{border:1px solid color-mix(in srgb,var(--color-primary-500) 30%,transparent)}.lc-chat__name{font-size:.6875rem;font-weight:600;color:var(--color-primary-500);margin-bottom:.125rem}.lc-chat__content{white-space:pre-wrap}.lc-chat__cursor{display:none}.lc-chat__cursor--visible{display:inline-block;width:2px;height:1em;background:currentColor;margin-left:1px;vertical-align:text-bottom;animation:blink-cursor .8s step-end infinite}@keyframes blink-cursor{0%,to{opacity:1}50%{opacity:0}}.lc-chat__time{font-size:.625rem;color:var(--color-neutral-400);margin-top:.25rem}.lc-chat__message--user .lc-chat__time{color:#ffffffb3;text-align:right}.lc-chat__typing{display:flex;gap:.25rem;padding:.25rem 0}.lc-chat__typing span{width:.375rem;height:.375rem;border-radius:50%;background:var(--color-neutral-400);animation:typing-bounce 1.4s ease-in-out infinite}.lc-chat__typing span:nth-child(2){animation-delay:.2s}.lc-chat__typing span:nth-child(3){animation-delay:.4s}@keyframes typing-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-.375rem)}}.lc-chat__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__input-area,:root[data-theme=dark] .lc-chat__input-area{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__input{flex:1;resize:none;border:1px solid var(--color-neutral-300);border-radius:1.25rem;padding:.5rem .875rem;font-size:.875rem;font-family:inherit;line-height:1.4;outline:none;background:var(--color-neutral-0, #fff);color:var(--color-neutral-900);max-height:6rem;overflow-y:auto;transition:border-color .15s}.lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__input:disabled{opacity:.5;cursor:not-allowed}.lc-chat__input::placeholder{color:var(--color-neutral-400)}[data-theme=dark] .lc-chat__input,:root[data-theme=dark] .lc-chat__input{border-color:var(--color-neutral-600);background:var(--color-neutral-700);color:var(--color-neutral-100)}[data-theme=dark] .lc-chat__input::placeholder,:root[data-theme=dark] .lc-chat__input::placeholder{color:var(--color-neutral-500)}[data-theme=dark] .lc-chat__input:focus,:root[data-theme=dark] .lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__send-btn{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:none;border-radius:50%;background:var(--color-primary-500);color:#fff;cursor:pointer;flex-shrink:0;transition:background .15s,opacity .15s}.lc-chat__send-btn:hover:not(:disabled){background:var(--color-primary-700)}.lc-chat__send-btn:disabled{opacity:.4;cursor:not-allowed}\n"] }]
10560
- }], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], isStreaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStreaming", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showAvatars: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAvatars", required: false }] }], showTimestamps: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimestamps", required: false }] }], messageSend: [{ type: i0.Output, args: ["messageSend"] }], scrollContainer: [{
10561
+ args: [{ selector: 'lc-chat', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"lc-chat\">\n @if (showHeader()) {\n <div class=\"lc-chat__header\">\n <span class=\"lc-chat__title\">{{ title() }}</span>\n @if (isStreaming()) {\n <span class=\"lc-chat__streaming-badge\">Streaming\u2026</span>\n }\n </div>\n }\n\n <div class=\"lc-chat__messages\" #scrollContainer>\n @for (msg of formattedMessages(); track trackById($index, msg)) {\n <div\n class=\"lc-chat__message\"\n [class.lc-chat__message--user]=\"msg.role === 'user'\"\n [class.lc-chat__message--agent]=\"msg.role === 'agent'\"\n [class.lc-chat__message--system]=\"msg.role === 'system'\"\n >\n @if (showAvatars() && msg.role !== 'system') {\n <div class=\"lc-chat__avatar\">\n @if (msg.avatar) {\n <img [src]=\"msg.avatar\" [alt]=\"msg.name || msg.role\" class=\"lc-chat__avatar-img\" />\n } @else {\n <span class=\"lc-chat__avatar-fallback\" [class.lc-chat__avatar-fallback--agent]=\"msg.role === 'agent'\">\n {{ (msg.name || msg.role).charAt(0).toUpperCase() }}\n </span>\n }\n </div>\n }\n\n <div class=\"lc-chat__bubble\" [class.lc-chat__bubble--streaming]=\"msg.streaming\">\n @if (msg.name && msg.role !== 'user') {\n <div class=\"lc-chat__name\">{{ msg.name }}</div>\n }\n <div class=\"lc-chat__content\">\n @if (messageTemplate) {\n <ng-container *ngTemplateOutlet=\"messageTemplate; context: { $implicit: msg }\" />\n } @else {\n {{ msg.content }}\n }\n <span class=\"lc-chat__cursor\" [class.lc-chat__cursor--visible]=\"msg.streaming\"></span>\n </div>\n @if (showTimestamps() && msg.timestamp) {\n <div class=\"lc-chat__time\">{{ formatTime(msg.timestamp) }}</div>\n }\n </div>\n </div>\n }\n\n @if (isStreaming() && !(formattedMessages().length && formattedMessages()[formattedMessages().length - 1].streaming)) {\n <div class=\"lc-chat__message lc-chat__message--agent\">\n @if (showAvatars()) {\n <div class=\"lc-chat__avatar\">\n <span class=\"lc-chat__avatar-fallback lc-chat__avatar-fallback--agent\">A</span>\n </div>\n }\n <div class=\"lc-chat__bubble\">\n <div class=\"lc-chat__typing\">\n <span></span><span></span><span></span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"lc-chat__input-area\">\n <textarea\n class=\"lc-chat__input\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputValue()\"\n [disabled]=\"disabled()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n rows=\"1\"\n ></textarea>\n <button\n class=\"lc-chat__send-btn\"\n [disabled]=\"!inputValue().trim() || disabled()\"\n (click)=\"send()\"\n aria-label=\"Send\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M3 10l7-7m0 0l7 7m-7-7v14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" transform=\"rotate(90 10 10)\"/>\n </svg>\n </button>\n </div>\n</div>\n", styles: [".lc-chat{display:flex;flex-direction:column;height:100%;min-height:20rem;border:1px solid var(--color-neutral-200);border-radius:.5rem;overflow:hidden;background:var(--color-neutral-0, #fff);font-family:var(--typography-font-family, sans-serif)}[data-theme=dark] .lc-chat,:root[data-theme=dark] .lc-chat{border-color:var(--color-neutral-700);background:var(--color-neutral-900)}.lc-chat__header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__header,:root[data-theme=dark] .lc-chat__header{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__title{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}[data-theme=dark] .lc-chat__title,:root[data-theme=dark] .lc-chat__title{color:var(--color-neutral-100)}.lc-chat__streaming-badge{font-size:.6875rem;padding:.125rem .5rem;background:color-mix(in srgb,var(--color-primary-500) 15%,transparent);color:var(--color-primary-500);border-radius:1rem;font-weight:500;animation:pulse-badge 1.5s infinite}@keyframes pulse-badge{0%,to{opacity:1}50%{opacity:.6}}.lc-chat__messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem}.lc-chat__message{display:flex;align-items:flex-start;gap:.5rem;max-width:85%}.lc-chat__message--user{align-self:flex-end;flex-direction:row-reverse}.lc-chat__message--agent{align-self:flex-start}.lc-chat__message--system{align-self:center;max-width:100%}.lc-chat__avatar{flex-shrink:0;width:2rem;height:2rem}.lc-chat__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.lc-chat__avatar-fallback{display:flex;align-items:center;justify-content:center;width:100%;height:100%;border-radius:50%;background:var(--color-neutral-300);color:#fff;font-size:.75rem;font-weight:700}.lc-chat__avatar-fallback--agent{background:var(--color-primary-500)}.lc-chat__bubble{padding:.5rem .75rem;border-radius:1rem;font-size:.875rem;line-height:1.5;word-break:break-word}.lc-chat__message--user .lc-chat__bubble{background:var(--color-primary-500);color:#fff;border-bottom-right-radius:.25rem}.lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-100);color:var(--color-neutral-900);border-bottom-left-radius:.25rem}[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble,:root[data-theme=dark] .lc-chat__message--agent .lc-chat__bubble{background:var(--color-neutral-800);color:var(--color-neutral-100)}.lc-chat__message--system .lc-chat__bubble{background:transparent;color:var(--color-neutral-500);font-size:.75rem;text-align:center;padding:.25rem .5rem}.lc-chat__message--agent .lc-chat__bubble--streaming{border:1px solid color-mix(in srgb,var(--color-primary-500) 30%,transparent)}.lc-chat__name{font-size:.6875rem;font-weight:600;color:var(--color-primary-500);margin-bottom:.125rem}.lc-chat__content{white-space:pre-wrap}.lc-chat__cursor{display:none}.lc-chat__cursor--visible{display:inline-block;width:2px;height:1em;background:currentColor;margin-left:1px;vertical-align:text-bottom;animation:blink-cursor .8s step-end infinite}@keyframes blink-cursor{0%,to{opacity:1}50%{opacity:0}}.lc-chat__time{font-size:.625rem;color:var(--color-neutral-400);margin-top:.25rem}.lc-chat__message--user .lc-chat__time{color:#ffffffb3;text-align:right}.lc-chat__typing{display:flex;gap:.25rem;padding:.25rem 0}.lc-chat__typing span{width:.375rem;height:.375rem;border-radius:50%;background:var(--color-neutral-400);animation:typing-bounce 1.4s ease-in-out infinite}.lc-chat__typing span:nth-child(2){animation-delay:.2s}.lc-chat__typing span:nth-child(3){animation-delay:.4s}@keyframes typing-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-.375rem)}}.lc-chat__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--color-neutral-200);background:var(--color-neutral-50)}[data-theme=dark] .lc-chat__input-area,:root[data-theme=dark] .lc-chat__input-area{border-color:var(--color-neutral-700);background:var(--color-neutral-800)}.lc-chat__input{flex:1;resize:none;border:1px solid var(--color-neutral-300);border-radius:1.25rem;padding:.5rem .875rem;font-size:.875rem;font-family:inherit;line-height:1.4;outline:none;background:var(--color-neutral-0, #fff);color:var(--color-neutral-900);max-height:6rem;overflow-y:auto;transition:border-color .15s}.lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__input:disabled{opacity:.5;cursor:not-allowed}.lc-chat__input::placeholder{color:var(--color-neutral-400)}[data-theme=dark] .lc-chat__input,:root[data-theme=dark] .lc-chat__input{border-color:var(--color-neutral-600);background:var(--color-neutral-700);color:var(--color-neutral-100)}[data-theme=dark] .lc-chat__input::placeholder,:root[data-theme=dark] .lc-chat__input::placeholder{color:var(--color-neutral-500)}[data-theme=dark] .lc-chat__input:focus,:root[data-theme=dark] .lc-chat__input:focus{border-color:var(--color-primary-500)}.lc-chat__send-btn{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:none;border-radius:50%;background:var(--color-primary-500);color:#fff;cursor:pointer;flex-shrink:0;transition:background .15s,opacity .15s}.lc-chat__send-btn:hover:not(:disabled){background:var(--color-primary-700)}.lc-chat__send-btn:disabled{opacity:.4;cursor:not-allowed}\n"] }]
10562
+ }], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], isStreaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStreaming", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showAvatars: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAvatars", required: false }] }], showTimestamps: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimestamps", required: false }] }], messageSend: [{ type: i0.Output, args: ["messageSend"] }], messageTemplate: [{
10563
+ type: ContentChild,
10564
+ args: ['messageTemplate']
10565
+ }], scrollContainer: [{
10561
10566
  type: ViewChild,
10562
10567
  args: ['scrollContainer']
10563
10568
  }] } });
@@ -12848,6 +12853,1089 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
12848
12853
  args: ['richArea']
12849
12854
  }] } });
12850
12855
 
12856
+ /**
12857
+ * Markdown renderer component.
12858
+ *
12859
+ * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with
12860
+ * optional syntax highlighting via `<lc-code-block>`.
12861
+ *
12862
+ * @example
12863
+ * ```html
12864
+ * <lc-markdown [content]="'# Hello World'" />
12865
+ * <lc-markdown [src]="'/docs/readme.md'" />
12866
+ * ```
12867
+ */
12868
+ class MarkdownComponent {
12869
+ sanitizer = inject(DomSanitizer);
12870
+ http = inject(HttpClient);
12871
+ httpSub;
12872
+ /** URL or path to load markdown from */
12873
+ src = input(...(ngDevMode ? [undefined, { debugName: "src" }] : /* istanbul ignore next */ []));
12874
+ /** Raw markdown string */
12875
+ content = input(...(ngDevMode ? [undefined, { debugName: "content" }] : /* istanbul ignore next */ []));
12876
+ /** Display variant */
12877
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
12878
+ /** Target for links */
12879
+ linkTarget = input('_self', ...(ngDevMode ? [{ debugName: "linkTarget" }] : /* istanbul ignore next */ []));
12880
+ /** Whether to sanitize HTML output */
12881
+ sanitize = input(true, ...(ngDevMode ? [{ debugName: "sanitize" }] : /* istanbul ignore next */ []));
12882
+ /** Whether to use code-block for fenced code */
12883
+ syntaxHighlight = input(true, ...(ngDevMode ? [{ debugName: "syntaxHighlight" }] : /* istanbul ignore next */ []));
12884
+ /** Whether to show anchor links on headings */
12885
+ showHeadingAnchors = input(false, ...(ngDevMode ? [{ debugName: "showHeadingAnchors" }] : /* istanbul ignore next */ []));
12886
+ /** Base URL for resolving relative links/images */
12887
+ baseUrl = input(...(ngDevMode ? [undefined, { debugName: "baseUrl" }] : /* istanbul ignore next */ []));
12888
+ /** Emitted when a link is clicked */
12889
+ linkClick = output();
12890
+ /** Emitted after rendering with heading TOC */
12891
+ rendered = output();
12892
+ /** Internal resolved markdown source */
12893
+ resolvedMarkdown = signal('', ...(ngDevMode ? [{ debugName: "resolvedMarkdown" }] : /* istanbul ignore next */ []));
12894
+ /** Parsed result (computed once from resolvedMarkdown) */
12895
+ parsed = computed(() => {
12896
+ const md = this.resolvedMarkdown();
12897
+ if (!md)
12898
+ return { html: '', blocks: [], headings: [] };
12899
+ return this.parseMarkdown(md);
12900
+ }, ...(ngDevMode ? [{ debugName: "parsed" }] : /* istanbul ignore next */ []));
12901
+ /** Computed render parts (HTML chunks + code blocks interleaved) */
12902
+ renderParts = computed(() => {
12903
+ const { html, blocks } = this.parsed();
12904
+ if (!html)
12905
+ return [];
12906
+ if (this.syntaxHighlight() && blocks.length > 0) {
12907
+ return this.splitIntoParts(html, blocks);
12908
+ }
12909
+ // No code blocks — single HTML part
12910
+ const sanitized = this.sanitize()
12911
+ ? this.sanitizer.sanitize(SecurityContext.HTML, html) || ''
12912
+ : html;
12913
+ return [{
12914
+ type: 'html',
12915
+ index: 0,
12916
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
12917
+ }];
12918
+ }, ...(ngDevMode ? [{ debugName: "renderParts" }] : /* istanbul ignore next */ []));
12919
+ containerClasses = computed(() => {
12920
+ return `lc-markdown lc-markdown--${this.variant()}`;
12921
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
12922
+ headings = computed(() => this.parsed().headings, ...(ngDevMode ? [{ debugName: "headings" }] : /* istanbul ignore next */ []));
12923
+ constructor() {
12924
+ // Load content from src when src changes
12925
+ effect(() => {
12926
+ const src = this.src();
12927
+ if (src) {
12928
+ this.loadFromUrl(src);
12929
+ }
12930
+ });
12931
+ // Use raw content when provided
12932
+ effect(() => {
12933
+ const content = this.content();
12934
+ if (content !== undefined) {
12935
+ this.resolvedMarkdown.set(content);
12936
+ }
12937
+ });
12938
+ // Emit rendered event when headings change
12939
+ effect(() => {
12940
+ const h = this.headings();
12941
+ if (h.length > 0) {
12942
+ this.rendered.emit({ headings: h });
12943
+ }
12944
+ });
12945
+ }
12946
+ ngOnDestroy() {
12947
+ this.httpSub?.unsubscribe();
12948
+ }
12949
+ onLinkClick(event) {
12950
+ const target = event.target;
12951
+ const anchor = target.closest('a');
12952
+ if (anchor) {
12953
+ this.linkClick.emit({ href: anchor.href, event });
12954
+ }
12955
+ }
12956
+ loadFromUrl(url) {
12957
+ this.httpSub?.unsubscribe();
12958
+ this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({
12959
+ next: (text) => this.resolvedMarkdown.set(text),
12960
+ error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),
12961
+ });
12962
+ }
12963
+ splitIntoParts(html, blocks) {
12964
+ const parts = [];
12965
+ // The placeholders survive HTML escaping as &lt;!--CODE_BLOCK_N--&gt;
12966
+ const regex = /&lt;!--CODE_BLOCK_(\d+)--&gt;/g;
12967
+ let lastIndex = 0;
12968
+ let partIndex = 0;
12969
+ let match;
12970
+ while ((match = regex.exec(html)) !== null) {
12971
+ const before = html.slice(lastIndex, match.index);
12972
+ if (before.trim()) {
12973
+ const sanitized = this.sanitize()
12974
+ ? this.sanitizer.sanitize(SecurityContext.HTML, before) || ''
12975
+ : before;
12976
+ parts.push({
12977
+ type: 'html',
12978
+ index: partIndex++,
12979
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
12980
+ });
12981
+ }
12982
+ const blockIdx = parseInt(match[1], 10);
12983
+ parts.push({
12984
+ type: 'code',
12985
+ index: partIndex++,
12986
+ code: blocks[blockIdx].code,
12987
+ lang: blocks[blockIdx].lang,
12988
+ });
12989
+ lastIndex = match.index + match[0].length;
12990
+ }
12991
+ const remaining = html.slice(lastIndex);
12992
+ if (remaining.trim()) {
12993
+ const sanitized = this.sanitize()
12994
+ ? this.sanitizer.sanitize(SecurityContext.HTML, remaining) || ''
12995
+ : remaining;
12996
+ parts.push({
12997
+ type: 'html',
12998
+ index: partIndex++,
12999
+ safeHtml: this.sanitizer.bypassSecurityTrustHtml(sanitized),
13000
+ });
13001
+ }
13002
+ return parts;
13003
+ }
13004
+ parseMarkdown(md) {
13005
+ const blocks = [];
13006
+ const headings = [];
13007
+ const baseUrl = this.baseUrl();
13008
+ const linkTarget = this.linkTarget();
13009
+ const showAnchors = this.showHeadingAnchors();
13010
+ // 1. Extract fenced code blocks → placeholders
13011
+ let processed = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
13012
+ const idx = blocks.length;
13013
+ blocks.push({ lang: lang || 'text', code: code.trimEnd() });
13014
+ return `<!--CODE_BLOCK_${idx}-->`;
13015
+ });
13016
+ // 2. Escape HTML entities
13017
+ processed = processed
13018
+ .replace(/&/g, '&amp;')
13019
+ .replace(/</g, '&lt;')
13020
+ .replace(/>/g, '&gt;');
13021
+ // 3. Headings (# to ######)
13022
+ processed = processed.replace(/^(#{1,6})\s+(.+)$/gm, (_match, hashes, text) => {
13023
+ const level = hashes.length;
13024
+ const id = text
13025
+ .toLowerCase()
13026
+ .replace(/[^\w\s-]/g, '')
13027
+ .replace(/\s+/g, '-');
13028
+ headings.push({ level, text, id });
13029
+ const anchor = showAnchors
13030
+ ? `<a href="#${id}" class="lc-markdown__anchor" aria-hidden="true">#</a>`
13031
+ : '';
13032
+ return `<h${level} id="${id}">${anchor}${text}</h${level}>`;
13033
+ });
13034
+ // 4. Horizontal rules
13035
+ processed = processed.replace(/^---+$/gm, '<hr>');
13036
+ // 5. Blockquotes
13037
+ processed = processed.replace(/^(?:&gt;)\s?(.*)$/gm, '<blockquote>$1</blockquote>');
13038
+ // Merge consecutive blockquotes
13039
+ processed = processed.replace(/<\/blockquote>\n<blockquote>/g, '\n');
13040
+ // 6. Tables (GFM)
13041
+ processed = this.parseTables(processed);
13042
+ // 7. Task lists
13043
+ processed = processed.replace(/^[-*]\s+\[x\]\s+(.*)$/gm, '<li class="lc-markdown__task"><input type="checkbox" checked disabled> $1</li>');
13044
+ processed = processed.replace(/^[-*]\s+\[ \]\s+(.*)$/gm, '<li class="lc-markdown__task"><input type="checkbox" disabled> $1</li>');
13045
+ // 8. Unordered lists
13046
+ processed = processed.replace(/^[-*]\s+(.*)$/gm, '<li>$1</li>');
13047
+ processed = processed.replace(/(<li>[\s\S]*?<\/li>)/g, (match) => {
13048
+ if (!match.includes('lc-markdown__task')) {
13049
+ return match;
13050
+ }
13051
+ return match;
13052
+ });
13053
+ // Wrap consecutive <li> in <ul>
13054
+ processed = processed.replace(/((?:<li[^>]*>.*<\/li>\n?)+)/g, '<ul>$1</ul>');
13055
+ // 9. Ordered lists
13056
+ processed = processed.replace(/^\d+\.\s+(.*)$/gm, '<li>$1</li>');
13057
+ // 10. Inline code
13058
+ processed = processed.replace(/`([^`]+)`/g, '<code>$1</code>');
13059
+ // 11. Bold + italic combos
13060
+ processed = processed.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
13061
+ processed = processed.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
13062
+ processed = processed.replace(/\*(.+?)\*/g, '<em>$1</em>');
13063
+ // 12. Strikethrough (GFM)
13064
+ processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');
13065
+ // 13. Images
13066
+ processed = processed.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, src) => {
13067
+ const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;
13068
+ return `<img src="${resolvedSrc}" alt="${alt}" class="lc-markdown__img">`;
13069
+ });
13070
+ // 14. Links
13071
+ processed = processed.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, href) => {
13072
+ const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')
13073
+ ? `${baseUrl}/${href}`
13074
+ : href;
13075
+ return `<a href="${resolvedHref}" target="${linkTarget}" rel="noopener">${text}</a>`;
13076
+ });
13077
+ // 15. Paragraphs — wrap standalone lines
13078
+ processed = processed
13079
+ .split('\n\n')
13080
+ .map((block) => {
13081
+ const trimmed = block.trim();
13082
+ if (!trimmed)
13083
+ return '';
13084
+ if (trimmed.startsWith('<h') ||
13085
+ trimmed.startsWith('<ul') ||
13086
+ trimmed.startsWith('<ol') ||
13087
+ trimmed.startsWith('<blockquote') ||
13088
+ trimmed.startsWith('<hr') ||
13089
+ trimmed.startsWith('<table') ||
13090
+ trimmed.startsWith('<!--CODE_BLOCK')) {
13091
+ return trimmed;
13092
+ }
13093
+ return `<p>${trimmed.replace(/\n/g, '<br>')}</p>`;
13094
+ })
13095
+ .join('\n');
13096
+ // 16. Restore code block placeholders
13097
+ if (this.syntaxHighlight()) {
13098
+ // Leave placeholders — the template will render <lc-code-block> for each
13099
+ // No replacement needed here
13100
+ }
13101
+ else {
13102
+ processed = processed.replace(/&lt;!--CODE_BLOCK_(\d+)--&gt;/g, (_match, idx) => {
13103
+ const block = blocks[parseInt(idx, 10)];
13104
+ return `<pre><code class="language-${block.lang}">${this.escapeHtml(block.code)}</code></pre>`;
13105
+ });
13106
+ }
13107
+ return { html: processed, blocks, headings };
13108
+ }
13109
+ parseTables(text) {
13110
+ return text.replace(/^(\|.+\|)\n(\|[-| :]+\|)\n((?:\|.+\|\n?)*)/gm, (_match, header, _separator, body) => {
13111
+ const headers = header
13112
+ .split('|')
13113
+ .filter((c) => c.trim())
13114
+ .map((c) => `<th>${c.trim()}</th>`)
13115
+ .join('');
13116
+ const rows = body
13117
+ .trim()
13118
+ .split('\n')
13119
+ .map((row) => {
13120
+ const cells = row
13121
+ .split('|')
13122
+ .filter((c) => c.trim())
13123
+ .map((c) => `<td>${c.trim()}</td>`)
13124
+ .join('');
13125
+ return `<tr>${cells}</tr>`;
13126
+ })
13127
+ .join('');
13128
+ return `<table class="lc-markdown__table"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>`;
13129
+ });
13130
+ }
13131
+ escapeHtml(text) {
13132
+ return text
13133
+ .replace(/&/g, '&amp;')
13134
+ .replace(/</g, '&lt;')
13135
+ .replace(/>/g, '&gt;');
13136
+ }
13137
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: MarkdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13138
+ 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 ul{list-style:disc}.lc-markdown ol{list-style:decimal}.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, encapsulation: i0.ViewEncapsulation.None });
13139
+ }
13140
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: MarkdownComponent, decorators: [{
13141
+ type: Component,
13142
+ args: [{ selector: 'lc-markdown', standalone: true, imports: [CodeBlockComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, 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 ul{list-style:disc}.lc-markdown ol{list-style:decimal}.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"] }]
13143
+ }], 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"] }] } });
13144
+
13145
+ /**
13146
+ * Streaming log / terminal viewer component.
13147
+ *
13148
+ * Supports controlled (lines input) and streaming (stream$ observable) modes,
13149
+ * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,
13150
+ * and filtering.
13151
+ *
13152
+ * @example
13153
+ * ```html
13154
+ * <lc-log-viewer [stream$]="logs$" autoScroll height="600px" variant="terminal" />
13155
+ * ```
13156
+ */
13157
+ class LogViewerComponent {
13158
+ scrollContainer;
13159
+ ngZone = inject(NgZone);
13160
+ streamSub;
13161
+ scrollListener;
13162
+ /** Controlled mode: array of log lines */
13163
+ lines = input([], ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
13164
+ /** Streaming mode: observable of log lines */
13165
+ stream$ = input(...(ngDevMode ? [undefined, { debugName: "stream$" }] : /* istanbul ignore next */ []));
13166
+ /** Maximum lines to keep in buffer */
13167
+ maxLines = input(10_000, ...(ngDevMode ? [{ debugName: "maxLines" }] : /* istanbul ignore next */ []));
13168
+ /** Auto-scroll to bottom on new lines */
13169
+ autoScroll = input(true, ...(ngDevMode ? [{ debugName: "autoScroll" }] : /* istanbul ignore next */ []));
13170
+ /** Show timestamps column */
13171
+ showTimestamps = input(true, ...(ngDevMode ? [{ debugName: "showTimestamps" }] : /* istanbul ignore next */ []));
13172
+ /** Show line numbers */
13173
+ showLineNumbers = input(false, ...(ngDevMode ? [{ debugName: "showLineNumbers" }] : /* istanbul ignore next */ []));
13174
+ /** Parse ANSI color codes */
13175
+ ansiColors = input(true, ...(ngDevMode ? [{ debugName: "ansiColors" }] : /* istanbul ignore next */ []));
13176
+ /** Filter by log levels */
13177
+ levelFilter = input(...(ngDevMode ? [undefined, { debugName: "levelFilter" }] : /* istanbul ignore next */ []));
13178
+ /** Search query to highlight */
13179
+ searchQuery = input(...(ngDevMode ? [undefined, { debugName: "searchQuery" }] : /* istanbul ignore next */ []));
13180
+ /** Container height */
13181
+ height = input('400px', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
13182
+ /** Visual variant */
13183
+ variant = input('log', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
13184
+ /** Emitted when a line is clicked */
13185
+ lineClick = output();
13186
+ /** Emitted on copy all */
13187
+ copyAll = output();
13188
+ /** Emitted when scroll state changes */
13189
+ scrollStateChange = output();
13190
+ /** Internal buffer of all lines */
13191
+ buffer = signal([], ...(ngDevMode ? [{ debugName: "buffer" }] : /* istanbul ignore next */ []));
13192
+ /** Whether stream is paused */
13193
+ paused = signal(false, ...(ngDevMode ? [{ debugName: "paused" }] : /* istanbul ignore next */ []));
13194
+ /** Whether user is at bottom of scroll */
13195
+ atBottom = signal(true, ...(ngDevMode ? [{ debugName: "atBottom" }] : /* istanbul ignore next */ []));
13196
+ /** Internal search input */
13197
+ internalSearch = signal('', ...(ngDevMode ? [{ debugName: "internalSearch" }] : /* istanbul ignore next */ []));
13198
+ /** Internal level filter */
13199
+ internalLevelFilter = signal(new Set(), ...(ngDevMode ? [{ debugName: "internalLevelFilter" }] : /* istanbul ignore next */ []));
13200
+ /** Show search bar */
13201
+ showSearch = signal(false, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
13202
+ /** Effective search query */
13203
+ effectiveSearch = computed(() => {
13204
+ return this.searchQuery() || this.internalSearch();
13205
+ }, ...(ngDevMode ? [{ debugName: "effectiveSearch" }] : /* istanbul ignore next */ []));
13206
+ /** Filtered lines for display */
13207
+ filteredLines = computed(() => {
13208
+ let lines = this.buffer();
13209
+ const levels = this.levelFilter();
13210
+ const internalFilter = this.internalLevelFilter();
13211
+ // Apply level filter
13212
+ if (levels?.length) {
13213
+ lines = lines.filter((l) => l.level && levels.includes(l.level));
13214
+ }
13215
+ else if (internalFilter.size > 0) {
13216
+ lines = lines.filter((l) => l.level && internalFilter.has(l.level));
13217
+ }
13218
+ return lines;
13219
+ }, ...(ngDevMode ? [{ debugName: "filteredLines" }] : /* istanbul ignore next */ []));
13220
+ /** Visible window of lines (virtualized) */
13221
+ scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
13222
+ LINE_HEIGHT = 22;
13223
+ visibleRange = computed(() => {
13224
+ const containerHeight = parseInt(this.height(), 10) || 400;
13225
+ const toolbarHeight = 40;
13226
+ const viewHeight = containerHeight - toolbarHeight;
13227
+ const total = this.filteredLines().length;
13228
+ const start = Math.floor(this.scrollTop() / this.LINE_HEIGHT);
13229
+ const visible = Math.ceil(viewHeight / this.LINE_HEIGHT) + 2;
13230
+ return {
13231
+ start: Math.max(0, start - 1),
13232
+ end: Math.min(total, start + visible + 1),
13233
+ total,
13234
+ totalHeight: total * this.LINE_HEIGHT,
13235
+ };
13236
+ }, ...(ngDevMode ? [{ debugName: "visibleRange" }] : /* istanbul ignore next */ []));
13237
+ visibleLines = computed(() => {
13238
+ const range = this.visibleRange();
13239
+ return this.filteredLines().slice(range.start, range.end).map((line, i) => ({
13240
+ ...line,
13241
+ _index: range.start + i,
13242
+ }));
13243
+ }, ...(ngDevMode ? [{ debugName: "visibleLines" }] : /* istanbul ignore next */ []));
13244
+ containerClasses = computed(() => {
13245
+ return `lc-log-viewer lc-log-viewer--${this.variant()}`;
13246
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
13247
+ /** Line counts per level for toolbar */
13248
+ levelCounts = computed(() => {
13249
+ const buf = this.buffer();
13250
+ const counts = { debug: 0, info: 0, warn: 0, error: 0 };
13251
+ for (const line of buf) {
13252
+ if (line.level && line.level in counts)
13253
+ counts[line.level]++;
13254
+ }
13255
+ return counts;
13256
+ }, ...(ngDevMode ? [{ debugName: "levelCounts" }] : /* istanbul ignore next */ []));
13257
+ constructor() {
13258
+ // Sync controlled lines to buffer
13259
+ effect(() => {
13260
+ const input = this.lines();
13261
+ if (input.length > 0) {
13262
+ this.buffer.set(input.slice(-this.maxLines()));
13263
+ }
13264
+ });
13265
+ // Subscribe to stream$
13266
+ effect(() => {
13267
+ const stream = this.stream$();
13268
+ this.streamSub?.unsubscribe();
13269
+ if (stream) {
13270
+ this.streamSub = stream.subscribe((data) => {
13271
+ if (this.paused())
13272
+ return;
13273
+ const newLines = Array.isArray(data) ? data : [data];
13274
+ this.buffer.update((buf) => {
13275
+ const combined = [...buf, ...newLines];
13276
+ return combined.length > this.maxLines()
13277
+ ? combined.slice(-this.maxLines())
13278
+ : combined;
13279
+ });
13280
+ });
13281
+ }
13282
+ });
13283
+ }
13284
+ ngAfterViewInit() {
13285
+ if (this.scrollContainer) {
13286
+ this.ngZone.runOutsideAngular(() => {
13287
+ this.scrollListener = () => this.onScroll();
13288
+ this.scrollContainer.nativeElement.addEventListener('scroll', this.scrollListener, { passive: true });
13289
+ });
13290
+ }
13291
+ }
13292
+ ngOnDestroy() {
13293
+ this.streamSub?.unsubscribe();
13294
+ if (this.scrollContainer && this.scrollListener) {
13295
+ this.scrollContainer.nativeElement.removeEventListener('scroll', this.scrollListener);
13296
+ }
13297
+ }
13298
+ onScroll() {
13299
+ const el = this.scrollContainer?.nativeElement;
13300
+ if (!el)
13301
+ return;
13302
+ this.scrollTop.set(el.scrollTop);
13303
+ const wasAtBottom = this.atBottom();
13304
+ const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30;
13305
+ if (wasAtBottom !== isAtBottom) {
13306
+ this.ngZone.run(() => {
13307
+ this.atBottom.set(isAtBottom);
13308
+ this.scrollStateChange.emit({ atBottom: isAtBottom });
13309
+ });
13310
+ }
13311
+ else {
13312
+ this.scrollTop.set(el.scrollTop);
13313
+ }
13314
+ }
13315
+ scrollToBottom() {
13316
+ const el = this.scrollContainer?.nativeElement;
13317
+ if (el) {
13318
+ el.scrollTop = el.scrollHeight;
13319
+ this.atBottom.set(true);
13320
+ }
13321
+ }
13322
+ togglePause() {
13323
+ this.paused.update((v) => !v);
13324
+ }
13325
+ clearBuffer() {
13326
+ this.buffer.set([]);
13327
+ }
13328
+ async onCopyAll() {
13329
+ const text = this.filteredLines()
13330
+ .map((l) => {
13331
+ const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';
13332
+ const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';
13333
+ return `${ts}${lvl}${l.text}`;
13334
+ })
13335
+ .join('\n');
13336
+ await navigator.clipboard.writeText(text);
13337
+ this.copyAll.emit(text);
13338
+ }
13339
+ toggleSearch() {
13340
+ this.showSearch.update((v) => !v);
13341
+ if (!this.showSearch()) {
13342
+ this.internalSearch.set('');
13343
+ }
13344
+ }
13345
+ toggleLevelFilter(level) {
13346
+ this.internalLevelFilter.update((set) => {
13347
+ const newSet = new Set(set);
13348
+ if (newSet.has(level)) {
13349
+ newSet.delete(level);
13350
+ }
13351
+ else {
13352
+ newSet.add(level);
13353
+ }
13354
+ return newSet;
13355
+ });
13356
+ }
13357
+ onLineClick(line) {
13358
+ this.lineClick.emit(line);
13359
+ }
13360
+ onKeydown(event) {
13361
+ if (event.key === '/' && !this.showSearch()) {
13362
+ event.preventDefault();
13363
+ this.toggleSearch();
13364
+ }
13365
+ if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {
13366
+ this.scrollContainer?.nativeElement.scrollTo(0, 0);
13367
+ }
13368
+ if (event.key === 'G' && !this.showSearch()) {
13369
+ this.scrollToBottom();
13370
+ }
13371
+ }
13372
+ formatTimestamp(date) {
13373
+ if (!date)
13374
+ return '';
13375
+ return date.toLocaleTimeString('en-US', { hour12: false });
13376
+ }
13377
+ parseAnsi(text) {
13378
+ if (!this.ansiColors())
13379
+ return this.escapeHtml(text);
13380
+ const escaped = this.escapeHtml(text);
13381
+ // Replace ANSI codes with spans
13382
+ return escaped
13383
+ .replace(/\x1b\[0m/g, '</span>')
13384
+ .replace(/\x1b\[1m/g, '<span class="ansi-bold">')
13385
+ .replace(/\x1b\[3m/g, '<span class="ansi-italic">')
13386
+ .replace(/\x1b\[30m/g, '<span class="ansi-black">')
13387
+ .replace(/\x1b\[31m/g, '<span class="ansi-red">')
13388
+ .replace(/\x1b\[32m/g, '<span class="ansi-green">')
13389
+ .replace(/\x1b\[33m/g, '<span class="ansi-yellow">')
13390
+ .replace(/\x1b\[34m/g, '<span class="ansi-blue">')
13391
+ .replace(/\x1b\[35m/g, '<span class="ansi-magenta">')
13392
+ .replace(/\x1b\[36m/g, '<span class="ansi-cyan">')
13393
+ .replace(/\x1b\[37m/g, '<span class="ansi-white">')
13394
+ .replace(/\x1b\[90m/g, '<span class="ansi-gray">')
13395
+ .replace(/\x1b\[91m/g, '<span class="ansi-bright-red">')
13396
+ .replace(/\x1b\[92m/g, '<span class="ansi-bright-green">')
13397
+ .replace(/\x1b\[93m/g, '<span class="ansi-bright-yellow">')
13398
+ .replace(/\x1b\[94m/g, '<span class="ansi-bright-blue">')
13399
+ .replace(/\x1b\[95m/g, '<span class="ansi-bright-magenta">')
13400
+ .replace(/\x1b\[96m/g, '<span class="ansi-bright-cyan">')
13401
+ .replace(/\x1b\[97m/g, '<span class="ansi-bright-white">')
13402
+ .replace(/\x1b\[\d+m/g, ''); // strip unrecognized codes
13403
+ }
13404
+ highlightSearch(html) {
13405
+ const query = this.effectiveSearch();
13406
+ if (!query)
13407
+ return html;
13408
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13409
+ return html.replace(new RegExp(`(${escaped})`, 'gi'), '<mark class="lc-log-viewer__match">$1</mark>');
13410
+ }
13411
+ escapeHtml(text) {
13412
+ return text
13413
+ .replace(/&/g, '&amp;')
13414
+ .replace(/</g, '&lt;')
13415
+ .replace(/>/g, '&gt;');
13416
+ }
13417
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: LogViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13418
+ 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, encapsulation: i0.ViewEncapsulation.None });
13419
+ }
13420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: LogViewerComponent, decorators: [{
13421
+ type: Component,
13422
+ args: [{ selector: 'lc-log-viewer', standalone: true, imports: [CommonModule, IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, 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"] }]
13423
+ }], ctorParameters: () => [], propDecorators: { scrollContainer: [{
13424
+ type: ViewChild,
13425
+ args: ['scrollContainer']
13426
+ }], 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"] }] } });
13427
+
13428
+ /**
13429
+ * Confirm dialog component for confirming user actions.
13430
+ *
13431
+ * Builds on `<lc-modal>` to provide a standardized confirmation pattern
13432
+ * with optional destructive variant and text-matching confirmation.
13433
+ *
13434
+ * @example
13435
+ * ```html
13436
+ * <lc-confirm-dialog
13437
+ * [open]="showConfirm()"
13438
+ * variant="destructive"
13439
+ * title="Delete project?"
13440
+ * message="This cannot be undone."
13441
+ * (confirmed)="onDelete()"
13442
+ * (cancelled)="showConfirm.set(false)"
13443
+ * />
13444
+ * ```
13445
+ */
13446
+ class ConfirmDialogComponent {
13447
+ confirmInput;
13448
+ /** Whether the dialog is open */
13449
+ open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
13450
+ /** Dialog variant */
13451
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
13452
+ /** Dialog title */
13453
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
13454
+ /** Dialog message */
13455
+ message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
13456
+ /** Confirm button label */
13457
+ confirmLabel = input('Confirm', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
13458
+ /** Cancel button label */
13459
+ cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
13460
+ /** Icon name (auto-set by variant if not provided) */
13461
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
13462
+ /** Require text match to enable confirm */
13463
+ requireText = input(...(ngDevMode ? [undefined, { debugName: "requireText" }] : /* istanbul ignore next */ []));
13464
+ /** Emitted when user confirms */
13465
+ confirmed = output();
13466
+ /** Emitted when user cancels */
13467
+ cancelled = output();
13468
+ /** Internal text input value */
13469
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
13470
+ /** Resolved icon name */
13471
+ resolvedIcon = computed(() => {
13472
+ const icon = this.icon();
13473
+ if (icon)
13474
+ return icon;
13475
+ switch (this.variant()) {
13476
+ case 'destructive':
13477
+ return 'exclamation-triangle';
13478
+ case 'warning':
13479
+ return 'exclamation-circle';
13480
+ default:
13481
+ return 'question-mark-circle';
13482
+ }
13483
+ }, ...(ngDevMode ? [{ debugName: "resolvedIcon" }] : /* istanbul ignore next */ []));
13484
+ /** Resolved icon color */
13485
+ iconColor = computed(() => {
13486
+ switch (this.variant()) {
13487
+ case 'destructive':
13488
+ return 'var(--color-error-default, #ef4444)';
13489
+ case 'warning':
13490
+ return 'var(--color-warning-default, #f59e0b)';
13491
+ default:
13492
+ return 'var(--color-primary-600, #2563eb)';
13493
+ }
13494
+ }, ...(ngDevMode ? [{ debugName: "iconColor" }] : /* istanbul ignore next */ []));
13495
+ /** Confirm button variant */
13496
+ confirmButtonVariant = computed(() => {
13497
+ switch (this.variant()) {
13498
+ case 'destructive':
13499
+ return 'danger';
13500
+ case 'warning':
13501
+ return 'warning';
13502
+ default:
13503
+ return 'primary';
13504
+ }
13505
+ }, ...(ngDevMode ? [{ debugName: "confirmButtonVariant" }] : /* istanbul ignore next */ []));
13506
+ /** Whether confirm is allowed (text match check) */
13507
+ confirmAllowed = computed(() => {
13508
+ const req = this.requireText();
13509
+ if (!req)
13510
+ return true;
13511
+ return this.inputValue() === req.expected;
13512
+ }, ...(ngDevMode ? [{ debugName: "confirmAllowed" }] : /* istanbul ignore next */ []));
13513
+ constructor() {
13514
+ // Reset input when dialog opens
13515
+ effect(() => {
13516
+ if (this.open()) {
13517
+ this.inputValue.set('');
13518
+ }
13519
+ });
13520
+ }
13521
+ onConfirm() {
13522
+ if (!this.confirmAllowed())
13523
+ return;
13524
+ this.confirmed.emit();
13525
+ }
13526
+ onCancel() {
13527
+ this.cancelled.emit();
13528
+ }
13529
+ onModalClose() {
13530
+ this.cancelled.emit();
13531
+ }
13532
+ onInputChange(value) {
13533
+ this.inputValue.set(String(value));
13534
+ }
13535
+ onKeydown(event) {
13536
+ if (event.key === 'Enter' && this.confirmAllowed()) {
13537
+ event.preventDefault();
13538
+ this.onConfirm();
13539
+ }
13540
+ }
13541
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13542
+ 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 });
13543
+ }
13544
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
13545
+ type: Component,
13546
+ 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"] }]
13547
+ }], ctorParameters: () => [], propDecorators: { confirmInput: [{
13548
+ type: ViewChild,
13549
+ args: ['confirmInput']
13550
+ }], 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"] }] } });
13551
+
13552
+ /**
13553
+ * Imperative confirm dialog service.
13554
+ *
13555
+ * @example
13556
+ * ```ts
13557
+ * const ok = await this.confirmService.confirm({
13558
+ * title: 'Delete item?',
13559
+ * message: 'This cannot be undone.',
13560
+ * });
13561
+ * if (ok) { ... }
13562
+ * ```
13563
+ */
13564
+ class ConfirmService {
13565
+ appRef = inject(ApplicationRef);
13566
+ injector = inject(EnvironmentInjector);
13567
+ /**
13568
+ * Show a confirmation dialog and return a promise that resolves
13569
+ * to true (confirmed) or false (cancelled).
13570
+ */
13571
+ confirm(opts) {
13572
+ return new Promise((resolve) => {
13573
+ // Create a host element
13574
+ const hostEl = document.createElement('div');
13575
+ document.body.appendChild(hostEl);
13576
+ const componentRef = createComponent(ConfirmDialogComponent, {
13577
+ hostElement: hostEl,
13578
+ environmentInjector: this.injector,
13579
+ });
13580
+ // Set inputs
13581
+ componentRef.setInput('open', true);
13582
+ componentRef.setInput('title', opts.title);
13583
+ componentRef.setInput('message', opts.message);
13584
+ if (opts.confirmLabel)
13585
+ componentRef.setInput('confirmLabel', opts.confirmLabel);
13586
+ if (opts.cancelLabel)
13587
+ componentRef.setInput('cancelLabel', opts.cancelLabel);
13588
+ if (opts.variant)
13589
+ componentRef.setInput('variant', opts.variant);
13590
+ if (opts.icon)
13591
+ componentRef.setInput('icon', opts.icon);
13592
+ if (opts.requireText)
13593
+ componentRef.setInput('requireText', opts.requireText);
13594
+ const cleanup = () => {
13595
+ this.appRef.detachView(componentRef.hostView);
13596
+ componentRef.destroy();
13597
+ hostEl.remove();
13598
+ };
13599
+ componentRef.instance.confirmed.subscribe(() => {
13600
+ cleanup();
13601
+ resolve(true);
13602
+ });
13603
+ componentRef.instance.cancelled.subscribe(() => {
13604
+ cleanup();
13605
+ resolve(false);
13606
+ });
13607
+ this.appRef.attachView(componentRef.hostView);
13608
+ });
13609
+ }
13610
+ /**
13611
+ * Show a destructive confirmation dialog.
13612
+ * Shorthand for `confirm({ ...opts, variant: 'destructive' })`.
13613
+ */
13614
+ destructive(opts) {
13615
+ return this.confirm({ ...opts, variant: 'destructive' });
13616
+ }
13617
+ /**
13618
+ * Show a warning confirmation dialog.
13619
+ * Shorthand for `confirm({ ...opts, variant: 'warning' })`.
13620
+ */
13621
+ warning(opts) {
13622
+ return this.confirm({ ...opts, variant: 'warning' });
13623
+ }
13624
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13625
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, providedIn: 'root' });
13626
+ }
13627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ConfirmService, decorators: [{
13628
+ type: Injectable,
13629
+ args: [{ providedIn: 'root' }]
13630
+ }] });
13631
+
13632
+ /**
13633
+ * Combobox / async autocomplete component.
13634
+ *
13635
+ * Supports free-text input with sync/async option suggestions,
13636
+ * single/multiple selection, create new entries, and keyboard navigation.
13637
+ *
13638
+ * @example
13639
+ * ```html
13640
+ * <lc-combobox
13641
+ * label="Assign to user"
13642
+ * [loadOptions]="searchUsers"
13643
+ * [(value)]="assignee"
13644
+ * allowCreate
13645
+ * />
13646
+ * ```
13647
+ */
13648
+ class ComboboxComponent {
13649
+ inputEl;
13650
+ elRef = inject(ElementRef);
13651
+ querySubject = new Subject();
13652
+ asyncSub;
13653
+ /** Sync options */
13654
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
13655
+ /** Async option loader */
13656
+ loadOptions = input(...(ngDevMode ? [undefined, { debugName: "loadOptions" }] : /* istanbul ignore next */ []));
13657
+ /** Debounce for async queries */
13658
+ debounceMs = input(250, ...(ngDevMode ? [{ debugName: "debounceMs" }] : /* istanbul ignore next */ []));
13659
+ /** Minimum characters before triggering search */
13660
+ minChars = input(1, ...(ngDevMode ? [{ debugName: "minChars" }] : /* istanbul ignore next */ []));
13661
+ /** Placeholder text */
13662
+ placeholder = input('Search…', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
13663
+ /** Label */
13664
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
13665
+ /** Helper text */
13666
+ helperText = input(...(ngDevMode ? [undefined, { debugName: "helperText" }] : /* istanbul ignore next */ []));
13667
+ /** Error message */
13668
+ error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
13669
+ /** Disabled state */
13670
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
13671
+ /** Allow multiple selections */
13672
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
13673
+ /** Allow creating new entries */
13674
+ allowCreate = input(false, ...(ngDevMode ? [{ debugName: "allowCreate" }] : /* istanbul ignore next */ []));
13675
+ /** Show loading spinner (controlled) */
13676
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
13677
+ /** Empty state message */
13678
+ emptyMessage = input('No results', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
13679
+ /** Size */
13680
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
13681
+ /** Value change event */
13682
+ valueChange = output();
13683
+ /** Query change event */
13684
+ queryChange = output();
13685
+ /** Option selected event */
13686
+ optionSelected = output();
13687
+ /** Created event (allowCreate) */
13688
+ created = output();
13689
+ /** Internal state */
13690
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
13691
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
13692
+ highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : /* istanbul ignore next */ []));
13693
+ asyncOptions = signal([], ...(ngDevMode ? [{ debugName: "asyncOptions" }] : /* istanbul ignore next */ []));
13694
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
13695
+ /** Selected values */
13696
+ selectedSingle = signal(null, ...(ngDevMode ? [{ debugName: "selectedSingle" }] : /* istanbul ignore next */ []));
13697
+ selectedMultiple = signal([], ...(ngDevMode ? [{ debugName: "selectedMultiple" }] : /* istanbul ignore next */ []));
13698
+ /** Resolved visible options */
13699
+ visibleOptions = computed(() => {
13700
+ const q = this.query().toLowerCase();
13701
+ const loader = this.loadOptions();
13702
+ let opts;
13703
+ if (loader) {
13704
+ opts = this.asyncOptions();
13705
+ }
13706
+ else {
13707
+ opts = this.options();
13708
+ if (q.length >= this.minChars()) {
13709
+ opts = opts.filter((o) => o.label.toLowerCase().includes(q) ||
13710
+ o.value.toLowerCase().includes(q) ||
13711
+ (o.description && o.description.toLowerCase().includes(q)));
13712
+ }
13713
+ }
13714
+ // Remove already selected in multiple mode
13715
+ if (this.multiple()) {
13716
+ const selected = new Set(this.selectedMultiple().map((s) => s.value));
13717
+ opts = opts.filter((o) => !selected.has(o.value));
13718
+ }
13719
+ return opts;
13720
+ }, ...(ngDevMode ? [{ debugName: "visibleOptions" }] : /* istanbul ignore next */ []));
13721
+ /** Grouped options */
13722
+ groupedOptions = computed(() => {
13723
+ const opts = this.visibleOptions();
13724
+ const groups = new Map();
13725
+ for (const opt of opts) {
13726
+ const group = opt.group || '';
13727
+ if (!groups.has(group))
13728
+ groups.set(group, []);
13729
+ groups.get(group).push(opt);
13730
+ }
13731
+ return Array.from(groups.entries()).map(([label, items]) => ({
13732
+ label,
13733
+ items,
13734
+ }));
13735
+ }, ...(ngDevMode ? [{ debugName: "groupedOptions" }] : /* istanbul ignore next */ []));
13736
+ /** Flat list for keyboard nav */
13737
+ flatOptions = computed(() => {
13738
+ return this.groupedOptions().flatMap((g) => g.items);
13739
+ }, ...(ngDevMode ? [{ debugName: "flatOptions" }] : /* istanbul ignore next */ []));
13740
+ /** Whether to show "Create" option */
13741
+ showCreateOption = computed(() => {
13742
+ if (!this.allowCreate())
13743
+ return false;
13744
+ const q = this.query().trim();
13745
+ if (!q)
13746
+ return false;
13747
+ const exact = this.visibleOptions().some((o) => o.label.toLowerCase() === q.toLowerCase());
13748
+ return !exact;
13749
+ }, ...(ngDevMode ? [{ debugName: "showCreateOption" }] : /* istanbul ignore next */ []));
13750
+ /** Container classes */
13751
+ containerClasses = computed(() => {
13752
+ const classes = [`lc-combobox`, `lc-combobox--${this.size()}`];
13753
+ if (this.disabled())
13754
+ classes.push('lc-combobox--disabled');
13755
+ if (this.error())
13756
+ classes.push('lc-combobox--error');
13757
+ if (this.isOpen())
13758
+ classes.push('lc-combobox--open');
13759
+ return classes.join(' ');
13760
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
13761
+ // ControlValueAccessor
13762
+ onChange = () => { };
13763
+ onTouched = () => { };
13764
+ constructor() {
13765
+ // Async loading pipe
13766
+ this.asyncSub = this.querySubject
13767
+ .pipe(debounceTime(this.debounceMs()), switchMap((q) => {
13768
+ const loader = this.loadOptions();
13769
+ if (!loader || q.length < this.minChars()) {
13770
+ return of([]);
13771
+ }
13772
+ this.isLoading.set(true);
13773
+ return loader(q);
13774
+ }))
13775
+ .subscribe((results) => {
13776
+ this.asyncOptions.set(results);
13777
+ this.isLoading.set(false);
13778
+ });
13779
+ }
13780
+ ngOnDestroy() {
13781
+ this.asyncSub?.unsubscribe();
13782
+ this.querySubject.complete();
13783
+ }
13784
+ writeValue(value) {
13785
+ if (this.multiple()) {
13786
+ this.selectedMultiple.set(Array.isArray(value) ? value : []);
13787
+ }
13788
+ else {
13789
+ this.selectedSingle.set(value && !Array.isArray(value) ? value : null);
13790
+ if (value && !Array.isArray(value)) {
13791
+ this.query.set(value.label);
13792
+ }
13793
+ }
13794
+ }
13795
+ registerOnChange(fn) {
13796
+ this.onChange = fn;
13797
+ }
13798
+ registerOnTouched(fn) {
13799
+ this.onTouched = fn;
13800
+ }
13801
+ setDisabledState(isDisabled) {
13802
+ // handled by input
13803
+ }
13804
+ onInputChange(event) {
13805
+ const value = event.target.value;
13806
+ this.query.set(value);
13807
+ this.queryChange.emit(value);
13808
+ this.highlightedIndex.set(-1);
13809
+ if (this.loadOptions()) {
13810
+ this.querySubject.next(value);
13811
+ }
13812
+ if (value.length >= this.minChars()) {
13813
+ this.isOpen.set(true);
13814
+ }
13815
+ }
13816
+ onInputFocus() {
13817
+ if (this.query().length >= this.minChars() || this.options().length > 0) {
13818
+ this.isOpen.set(true);
13819
+ }
13820
+ }
13821
+ onInputBlur() {
13822
+ this.onTouched();
13823
+ // Delay to allow click on dropdown
13824
+ setTimeout(() => this.isOpen.set(false), 200);
13825
+ }
13826
+ onKeydown(event) {
13827
+ const flat = this.flatOptions();
13828
+ const total = flat.length + (this.showCreateOption() ? 1 : 0);
13829
+ switch (event.key) {
13830
+ case 'ArrowDown':
13831
+ event.preventDefault();
13832
+ this.isOpen.set(true);
13833
+ this.highlightedIndex.update((i) => (i + 1) % total);
13834
+ break;
13835
+ case 'ArrowUp':
13836
+ event.preventDefault();
13837
+ this.highlightedIndex.update((i) => (i - 1 + total) % total);
13838
+ break;
13839
+ case 'Enter':
13840
+ event.preventDefault();
13841
+ if (this.highlightedIndex() >= 0) {
13842
+ if (this.highlightedIndex() === flat.length && this.showCreateOption()) {
13843
+ this.onCreateNew();
13844
+ }
13845
+ else {
13846
+ const opt = flat[this.highlightedIndex()];
13847
+ if (opt && !opt.disabled) {
13848
+ this.selectOption(opt);
13849
+ }
13850
+ }
13851
+ }
13852
+ else if (this.showCreateOption()) {
13853
+ this.onCreateNew();
13854
+ }
13855
+ break;
13856
+ case 'Escape':
13857
+ this.isOpen.set(false);
13858
+ break;
13859
+ case 'Tab':
13860
+ if (this.highlightedIndex() >= 0 && this.isOpen()) {
13861
+ const opt = flat[this.highlightedIndex()];
13862
+ if (opt && !opt.disabled) {
13863
+ this.selectOption(opt);
13864
+ }
13865
+ }
13866
+ this.isOpen.set(false);
13867
+ break;
13868
+ case 'Backspace':
13869
+ if (this.multiple() && !this.query() && this.selectedMultiple().length > 0) {
13870
+ this.removeSelected(this.selectedMultiple()[this.selectedMultiple().length - 1]);
13871
+ }
13872
+ break;
13873
+ }
13874
+ }
13875
+ selectOption(option) {
13876
+ if (this.multiple()) {
13877
+ this.selectedMultiple.update((sel) => [...sel, option]);
13878
+ this.query.set('');
13879
+ const value = this.selectedMultiple();
13880
+ this.onChange(value);
13881
+ this.valueChange.emit(value);
13882
+ }
13883
+ else {
13884
+ this.selectedSingle.set(option);
13885
+ this.query.set(option.label);
13886
+ this.onChange(option);
13887
+ this.valueChange.emit(option);
13888
+ }
13889
+ this.optionSelected.emit(option);
13890
+ this.isOpen.set(false);
13891
+ this.highlightedIndex.set(-1);
13892
+ }
13893
+ removeSelected(option) {
13894
+ this.selectedMultiple.update((sel) => sel.filter((s) => s.value !== option.value));
13895
+ const value = this.selectedMultiple();
13896
+ this.onChange(value);
13897
+ this.valueChange.emit(value);
13898
+ this.inputEl?.nativeElement.focus();
13899
+ }
13900
+ onCreateNew() {
13901
+ const q = this.query().trim();
13902
+ if (!q)
13903
+ return;
13904
+ const newOption = { value: q, label: q };
13905
+ this.created.emit(q);
13906
+ this.selectOption(newOption);
13907
+ }
13908
+ onDocumentClick(event) {
13909
+ if (!this.elRef.nativeElement.contains(event.target)) {
13910
+ this.isOpen.set(false);
13911
+ }
13912
+ }
13913
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13914
+ 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: [
13915
+ {
13916
+ provide: NG_VALUE_ACCESSOR,
13917
+ useExisting: forwardRef(() => ComboboxComponent),
13918
+ multi: true,
13919
+ },
13920
+ ], 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 });
13921
+ }
13922
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: ComboboxComponent, decorators: [{
13923
+ type: Component,
13924
+ args: [{ selector: 'lc-combobox', standalone: true, imports: [CommonModule, IconComponent, SpinnerComponent, ChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
13925
+ {
13926
+ provide: NG_VALUE_ACCESSOR,
13927
+ useExisting: forwardRef(() => ComboboxComponent),
13928
+ multi: true,
13929
+ },
13930
+ ], 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"] }]
13931
+ }], ctorParameters: () => [], propDecorators: { inputEl: [{
13932
+ type: ViewChild,
13933
+ args: ['inputEl']
13934
+ }], 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: [{
13935
+ type: HostListener,
13936
+ args: ['document:click', ['$event']]
13937
+ }] } });
13938
+
12851
13939
  /**
12852
13940
  * Life-Cockpit Design System (UI Kit)
12853
13941
  *
@@ -12870,5 +13958,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
12870
13958
  * Generated bundle index. Do not edit.
12871
13959
  */
12872
13960
 
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 };
13961
+ 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
13962
  //# sourceMappingURL=life-cockpit-angular-ui-kit.mjs.map