@lifecorner/angular-ui-kit 3.22.2 → 3.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-diff-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-diff-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/theme-contrast.spec.ts +377 -6
|
@@ -86,7 +86,7 @@ var DescriptionListComponent = class DescriptionListComponent {
|
|
|
86
86
|
outputs: { itemClick: "itemClick" },
|
|
87
87
|
ngImport: i0,
|
|
88
88
|
template: "<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term) {\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n [attr.role]=\"clickable() ? 'button' : null\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n </dd>\n </div>\n }\n</dl>\n",
|
|
89
|
-
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__row--clickable:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-dl__term{flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-primary-500);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-primary-500);text-decoration:none}.lc-dl__link:hover{text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}\n"],
|
|
89
|
+
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__row--clickable:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-primary-500);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-primary-500);text-decoration:none}.lc-dl__link:hover{text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}\n"],
|
|
90
90
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
91
91
|
});
|
|
92
92
|
};
|
|
@@ -102,7 +102,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
102
102
|
standalone: true,
|
|
103
103
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
104
104
|
template: "<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term) {\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n [attr.role]=\"clickable() ? 'button' : null\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n </dd>\n </div>\n }\n</dl>\n",
|
|
105
|
-
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__row--clickable:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-dl__term{flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-primary-500);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-primary-500);text-decoration:none}.lc-dl__link:hover{text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}\n"]
|
|
105
|
+
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__row--clickable:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-primary-500);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-primary-500);text-decoration:none}.lc-dl__link:hover{text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}\n"]
|
|
106
106
|
}]
|
|
107
107
|
}],
|
|
108
108
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-description-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.ts","../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\n\n/**\n * Emphasis treatment applied to a row's value.\n * - `mono` renders the value in the monospace font (paths, URLs, hashes).\n */\nexport type DescriptionListEmphasis =\n | 'default'\n | 'strong'\n | 'muted'\n | 'primary'\n | 'mono';\n\nexport interface DescriptionListItem {\n /** Label shown on the term side of the row. */\n term: string;\n /** Value shown on the description side of the row. */\n value: string;\n /**\n * Optional qualifying suffix appended after the value on the same line and\n * rendered muted (e.g. a rating grade or \"last checked\" hint). Wraps if the\n * combined value + suffix exceed the available width.\n */\n valueSuffix?: string;\n /** Optional link — renders the value as an anchor. */\n href?: string;\n /**\n * Machine-readable date or time of the value (ISO 8601, e.g.\n * `2026-10-12T18:05:00+02:00`): renders the value inside\n * `<time datetime>`, so assistive tech and parsers get the exact moment\n * behind „heute 18:05“. Ignored together with `href`.\n */\n datetime?: string;\n /** Optional visual emphasis for the value. @default 'default' */\n emphasis?: DescriptionListEmphasis;\n /** Optional opaque payload echoed back in `itemClick`. */\n id?: string;\n}\n\nexport type DescriptionListLayout = 'rows' | 'stacked';\nexport type DescriptionListSize = 'sm' | 'md';\n\n/**\n * Per-row separator style (applies to the `rows` layout).\n * - `line` solid hairline under each row (default — unchanged look)\n * - `divider` subtle dashed line, for the dense card look\n * - `none` no row separator\n *\n * Orthogonal to `leaders`, which draws dotted leaders *between* term and value.\n */\nexport type DescriptionListSeparator = 'line' | 'divider' | 'none';\n\n/**\n * Description list for key/value metadata (a styled `<dl>`).\n *\n * Features:\n * - `rows` layout: term on the left, value on the right, with an optional\n * dotted leader line connecting them (the DS2.0 \"spec sheet\" look).\n * - `stacked` layout: term above value, for narrow columns.\n * - Per-row value emphasis and optional links.\n * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`\n * rows are focusable and keyboard-activatable.\n *\n * @example\n * ```html\n * <lc-description-list\n * [items]=\"[\n * { term: 'Repository', value: 'example/project', href: '#', emphasis: 'primary' },\n * { term: 'Access', value: 'Token stored · read only' },\n * { term: 'Status', value: 'Maintained', emphasis: 'strong' }\n * ]\"\n * [leaders]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-description-list',\n standalone: true,\n templateUrl: './description-list.component.html',\n styleUrls: ['./description-list.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DescriptionListComponent {\n /** Rows to render. */\n readonly items = input.required<readonly DescriptionListItem[]>();\n\n /**\n * Row layout.\n * - rows: term left / value right (default)\n * - stacked: term above value\n * @default 'rows'\n */\n readonly layout = input<DescriptionListLayout>('rows');\n\n /**\n * Draw a dotted leader line between term and value. Only applies to the\n * `rows` layout.\n * @default false\n */\n readonly leaders = input<boolean>(false);\n\n /** Density of the rows. @default 'md' */\n readonly size = input<DescriptionListSize>('md');\n\n /**\n * Per-row separator style for the `rows` layout. Defaults to `line`, which is\n * identical to the previous behavior. Orthogonal to `leaders`.\n * @default 'line'\n */\n readonly separator = input<DescriptionListSeparator>('line');\n\n /**\n * Whether rows are interactive. When set, each row is focusable and\n * Enter / Space emit `itemClick`, so the click handler is reachable from\n * the keyboard. Set this whenever you handle `itemClick`.\n * @default false\n */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */\n readonly itemClick = output<DescriptionListItem>();\n\n protected readonly hostClasses = computed(() =>\n [\n 'lc-dl',\n `lc-dl--${this.layout()}`,\n `lc-dl--${this.size()}`,\n `lc-dl--sep-${this.separator()}`,\n this.leaders() && this.layout() === 'rows' ? 'lc-dl--leaders' : '',\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n protected valueClass(item: DescriptionListItem): string {\n return `lc-dl__value lc-dl__value--${item.emphasis ?? 'default'}`;\n }\n\n /** Keyboard activation of a clickable row (only from the row itself, not a nested link). */\n protected onRowKeydown(event: KeyboardEvent, item: DescriptionListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.itemClick.emit(item);\n }\n }\n}\n","<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term) {\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n [attr.role]=\"clickable() ? 'button' : null\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n </dd>\n </div>\n }\n</dl>\n"],"mappings":";;AAuFA,IAAa,2BAAb,MAAa,yBAAwB;CAE1B,QAAQ,MAAM,SAA0C,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQxD,SAAS,MAA6B,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAO7C,UAAU,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,OAAO,MAA2B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAOvC,YAAY,MAAgC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAQnD,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAER,cAAc,eAC/B;EACE;EACA,UAAU,KAAK,OAAM;EACrB,UAAU,KAAK,KAAI;EACnB,cAAc,KAAK,UAAS;EAC5B,KAAK,QAAO,KAAM,KAAK,OAAM,MAAO,SAAS,mBAAmB;CACjE,CAAA,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,GACZ,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAES,WAAW,MAAyB;EAC5C,OAAO,8BAA8B,KAAK,YAAY;CACxD;CAGU,aAAa,OAAsB,MAAyB;EACpE,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CA/DW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAwB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAxB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAwB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UCvFrC;EA2BA,QAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-description-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.ts","../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\n\n/**\n * Emphasis treatment applied to a row's value.\n * - `mono` renders the value in the monospace font (paths, URLs, hashes).\n */\nexport type DescriptionListEmphasis =\n | 'default'\n | 'strong'\n | 'muted'\n | 'primary'\n | 'mono';\n\nexport interface DescriptionListItem {\n /** Label shown on the term side of the row. */\n term: string;\n /** Value shown on the description side of the row. */\n value: string;\n /**\n * Optional qualifying suffix appended after the value on the same line and\n * rendered muted (e.g. a rating grade or \"last checked\" hint). Wraps if the\n * combined value + suffix exceed the available width.\n */\n valueSuffix?: string;\n /** Optional link — renders the value as an anchor. */\n href?: string;\n /**\n * Machine-readable date or time of the value (ISO 8601, e.g.\n * `2026-10-12T18:05:00+02:00`): renders the value inside\n * `<time datetime>`, so assistive tech and parsers get the exact moment\n * behind „heute 18:05“. Ignored together with `href`.\n */\n datetime?: string;\n /** Optional visual emphasis for the value. @default 'default' */\n emphasis?: DescriptionListEmphasis;\n /** Optional opaque payload echoed back in `itemClick`. */\n id?: string;\n}\n\nexport type DescriptionListLayout = 'rows' | 'stacked';\nexport type DescriptionListSize = 'sm' | 'md';\n\n/**\n * Per-row separator style (applies to the `rows` layout).\n * - `line` solid hairline under each row (default — unchanged look)\n * - `divider` subtle dashed line, for the dense card look\n * - `none` no row separator\n *\n * Orthogonal to `leaders`, which draws dotted leaders *between* term and value.\n */\nexport type DescriptionListSeparator = 'line' | 'divider' | 'none';\n\n/**\n * Description list for key/value metadata (a styled `<dl>`).\n *\n * Features:\n * - `rows` layout: term on the left, value on the right, with an optional\n * dotted leader line connecting them (the DS2.0 \"spec sheet\" look).\n * - `stacked` layout: term above value, for narrow columns.\n * - Per-row value emphasis and optional links.\n * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`\n * rows are focusable and keyboard-activatable.\n *\n * @example\n * ```html\n * <lc-description-list\n * [items]=\"[\n * { term: 'Repository', value: 'example/project', href: '#', emphasis: 'primary' },\n * { term: 'Access', value: 'Token stored · read only' },\n * { term: 'Status', value: 'Maintained', emphasis: 'strong' }\n * ]\"\n * [leaders]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-description-list',\n standalone: true,\n templateUrl: './description-list.component.html',\n styleUrls: ['./description-list.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DescriptionListComponent {\n /** Rows to render. */\n readonly items = input.required<readonly DescriptionListItem[]>();\n\n /**\n * Row layout.\n * - rows: term left / value right (default)\n * - stacked: term above value\n * @default 'rows'\n */\n readonly layout = input<DescriptionListLayout>('rows');\n\n /**\n * Draw a dotted leader line between term and value. Only applies to the\n * `rows` layout.\n * @default false\n */\n readonly leaders = input<boolean>(false);\n\n /** Density of the rows. @default 'md' */\n readonly size = input<DescriptionListSize>('md');\n\n /**\n * Per-row separator style for the `rows` layout. Defaults to `line`, which is\n * identical to the previous behavior. Orthogonal to `leaders`.\n * @default 'line'\n */\n readonly separator = input<DescriptionListSeparator>('line');\n\n /**\n * Whether rows are interactive. When set, each row is focusable and\n * Enter / Space emit `itemClick`, so the click handler is reachable from\n * the keyboard. Set this whenever you handle `itemClick`.\n * @default false\n */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */\n readonly itemClick = output<DescriptionListItem>();\n\n protected readonly hostClasses = computed(() =>\n [\n 'lc-dl',\n `lc-dl--${this.layout()}`,\n `lc-dl--${this.size()}`,\n `lc-dl--sep-${this.separator()}`,\n this.leaders() && this.layout() === 'rows' ? 'lc-dl--leaders' : '',\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n protected valueClass(item: DescriptionListItem): string {\n return `lc-dl__value lc-dl__value--${item.emphasis ?? 'default'}`;\n }\n\n /** Keyboard activation of a clickable row (only from the row itself, not a nested link). */\n protected onRowKeydown(event: KeyboardEvent, item: DescriptionListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.itemClick.emit(item);\n }\n }\n}\n","<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term) {\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n [attr.role]=\"clickable() ? 'button' : null\"\n [attr.tabindex]=\"clickable() ? 0 : null\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n </dd>\n </div>\n }\n</dl>\n"],"mappings":";;AAuFA,IAAa,2BAAb,MAAa,yBAAwB;CAE1B,QAAQ,MAAM,SAA0C,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQxD,SAAS,MAA6B,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAO7C,UAAU,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,OAAO,MAA2B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAOvC,YAAY,MAAgC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAQnD,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAER,cAAc,eAC/B;EACE;EACA,UAAU,KAAK,OAAM;EACrB,UAAU,KAAK,KAAI;EACnB,cAAc,KAAK,UAAS;EAC5B,KAAK,QAAO,KAAM,KAAK,OAAM,MAAO,SAAS,mBAAmB;CACjE,CAAA,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,GACZ,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAES,WAAW,MAAyB;EAC5C,OAAO,8BAA8B,KAAK,YAAY;CACxD;CAGU,aAAa,OAAsB,MAAyB;EACpE,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CA/DW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAwB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAxB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAwB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UCvFrC;EA2BA,QAAA,CAAA,soEAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AD4Da,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAwB,YAAA,CAAA;EAPpC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAqB,YACnB;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,soEAAA;EAAA,CAAA"}
|
|
@@ -265,7 +265,7 @@ var DiffViewerComponent = class DiffViewerComponent {
|
|
|
265
265
|
},
|
|
266
266
|
ngImport: i0,
|
|
267
267
|
template: "<div class=\"lc-diff\">\n <!-- Stats bar -->\n <div class=\"lc-diff__stats\">\n <span class=\"lc-diff__stat lc-diff__stat--add\">+{{ stats().additions }}</span>\n <span class=\"lc-diff__stat lc-diff__stat--del\">−{{ stats().deletions }}</span>\n </div>\n\n @if (tooLarge()) {\n <!-- Size cap hit: no diff is computed -->\n <p class=\"lc-diff__too-large\" role=\"status\">\n Diff too large to display ({{ oldLabel() }}: {{ oldLineCount() }} lines,\n {{ newLabel() }}: {{ newLineCount() }} lines; limit {{ maxLines() }} lines per side).\n </p>\n } @else if (mode() === 'inline') {\n <!-- Inline mode -->\n <div class=\"lc-diff__inline\">\n <div class=\"lc-diff__headers\">\n <span class=\"lc-diff__file-label\">{{ oldLabel() }} → {{ newLabel() }}</span>\n </div>\n <div class=\"lc-diff__body\">\n @for (line of filteredLines(); track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num lc-diff__num--left\">{{ line.leftNum ?? '' }}</span>\n <span class=\"lc-diff__num lc-diff__num--right\">{{ line.rightNum ?? '' }}</span>\n }\n <span class=\"lc-diff__prefix\">{{ line.type === 'added' ? '+' : line.type === 'removed' ? '−' : ' ' }}</span>\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- Side-by-side mode -->\n <div class=\"lc-diff__side-by-side\">\n <div class=\"lc-diff__pane lc-diff__pane--left\">\n <div class=\"lc-diff__pane-header\">{{ oldLabel() }}</div>\n @for (line of sideBySide().left; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n <div class=\"lc-diff__pane lc-diff__pane--right\">\n <div class=\"lc-diff__pane-header\">{{ newLabel() }}</div>\n @for (line of sideBySide().right; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n }\n</div>\n",
|
|
268
|
-
styles: [".lc-diff{border:1px solid var(--color-border);border-radius:8px;overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;line-height:1.5}.lc-diff__stats{display:flex;gap:8px;padding:6px 12px;background:var(--color-surface-2);border-bottom:1px solid var(--color-border)}.lc-diff__stat{font-size:.6875rem;font-weight:600}.lc-diff__stat--add{color:var(--color-on-success-subtle)}.lc-diff__stat--del{color:var(--color-on-error-subtle)}.lc-diff__headers,.lc-diff__pane-header{padding:4px 12px;background:var(--color-surface-2);font-size:.6875rem;font-weight:600;color:var(--color-text-secondary);border-bottom:1px solid var(--color-border)}.lc-diff__body{overflow-x:auto}.lc-diff__side-by-side{display:grid;grid-template-columns:1fr 1fr}.lc-diff__pane{overflow-x:auto;min-width:0}.lc-diff__pane--left{border-right:1px solid var(--color-border)}.lc-diff__line{display:flex;min-height:1.5em;padding:0 8px}.lc-diff__line--added{background:var(--color-success-
|
|
268
|
+
styles: [".lc-diff{border:1px solid var(--color-border);border-radius:8px;overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;line-height:1.5}.lc-diff__stats{display:flex;gap:8px;padding:6px 12px;background:var(--color-surface-2);border-bottom:1px solid var(--color-border)}.lc-diff__stat{font-size:.6875rem;font-weight:600}.lc-diff__stat--add{color:var(--color-on-success-subtle)}.lc-diff__stat--del{color:var(--color-on-error-subtle)}.lc-diff__headers,.lc-diff__pane-header{padding:4px 12px;background:var(--color-surface-2);font-size:.6875rem;font-weight:600;color:var(--color-text-secondary);border-bottom:1px solid var(--color-border)}.lc-diff__body{overflow-x:auto}.lc-diff__side-by-side{display:grid;grid-template-columns:1fr 1fr}.lc-diff__pane{overflow-x:auto;min-width:0}.lc-diff__pane--left{border-right:1px solid var(--color-border)}.lc-diff__line{display:flex;min-height:1.5em;padding:0 8px}.lc-diff__line--added{background:var(--color-success-subtle)}.lc-diff__line--removed{background:var(--color-error-subtle)}.lc-diff__line--empty{background:var(--color-surface-2)}.lc-diff__num{flex-shrink:0;width:36px;text-align:right;padding-right:8px;color:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.lc-diff__num--left,.lc-diff__num--right{width:36px}.lc-diff__prefix{flex-shrink:0;width:16px;text-align:center;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none;font-weight:600}.lc-diff__line--added .lc-diff__prefix{color:var(--color-on-success-subtle)}.lc-diff__line--removed .lc-diff__prefix{color:var(--color-on-error-subtle)}.lc-diff__content{flex:1;white-space:pre;color:var(--color-text-primary)}.lc-diff__file-label{color:var(--color-text-secondary)}.lc-diff__too-large{margin:0;padding:12px;font-family:var(--font-family-sans, sans-serif);font-size:.8125rem;color:var(--color-text-secondary);background:var(--color-surface-2)}\n"],
|
|
269
269
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
270
270
|
});
|
|
271
271
|
};
|
|
@@ -281,7 +281,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
281
281
|
standalone: true,
|
|
282
282
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
283
283
|
template: "<div class=\"lc-diff\">\n <!-- Stats bar -->\n <div class=\"lc-diff__stats\">\n <span class=\"lc-diff__stat lc-diff__stat--add\">+{{ stats().additions }}</span>\n <span class=\"lc-diff__stat lc-diff__stat--del\">−{{ stats().deletions }}</span>\n </div>\n\n @if (tooLarge()) {\n <!-- Size cap hit: no diff is computed -->\n <p class=\"lc-diff__too-large\" role=\"status\">\n Diff too large to display ({{ oldLabel() }}: {{ oldLineCount() }} lines,\n {{ newLabel() }}: {{ newLineCount() }} lines; limit {{ maxLines() }} lines per side).\n </p>\n } @else if (mode() === 'inline') {\n <!-- Inline mode -->\n <div class=\"lc-diff__inline\">\n <div class=\"lc-diff__headers\">\n <span class=\"lc-diff__file-label\">{{ oldLabel() }} → {{ newLabel() }}</span>\n </div>\n <div class=\"lc-diff__body\">\n @for (line of filteredLines(); track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num lc-diff__num--left\">{{ line.leftNum ?? '' }}</span>\n <span class=\"lc-diff__num lc-diff__num--right\">{{ line.rightNum ?? '' }}</span>\n }\n <span class=\"lc-diff__prefix\">{{ line.type === 'added' ? '+' : line.type === 'removed' ? '−' : ' ' }}</span>\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- Side-by-side mode -->\n <div class=\"lc-diff__side-by-side\">\n <div class=\"lc-diff__pane lc-diff__pane--left\">\n <div class=\"lc-diff__pane-header\">{{ oldLabel() }}</div>\n @for (line of sideBySide().left; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n <div class=\"lc-diff__pane lc-diff__pane--right\">\n <div class=\"lc-diff__pane-header\">{{ newLabel() }}</div>\n @for (line of sideBySide().right; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n }\n</div>\n",
|
|
284
|
-
styles: [".lc-diff{border:1px solid var(--color-border);border-radius:8px;overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;line-height:1.5}.lc-diff__stats{display:flex;gap:8px;padding:6px 12px;background:var(--color-surface-2);border-bottom:1px solid var(--color-border)}.lc-diff__stat{font-size:.6875rem;font-weight:600}.lc-diff__stat--add{color:var(--color-on-success-subtle)}.lc-diff__stat--del{color:var(--color-on-error-subtle)}.lc-diff__headers,.lc-diff__pane-header{padding:4px 12px;background:var(--color-surface-2);font-size:.6875rem;font-weight:600;color:var(--color-text-secondary);border-bottom:1px solid var(--color-border)}.lc-diff__body{overflow-x:auto}.lc-diff__side-by-side{display:grid;grid-template-columns:1fr 1fr}.lc-diff__pane{overflow-x:auto;min-width:0}.lc-diff__pane--left{border-right:1px solid var(--color-border)}.lc-diff__line{display:flex;min-height:1.5em;padding:0 8px}.lc-diff__line--added{background:var(--color-success-
|
|
284
|
+
styles: [".lc-diff{border:1px solid var(--color-border);border-radius:8px;overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;line-height:1.5}.lc-diff__stats{display:flex;gap:8px;padding:6px 12px;background:var(--color-surface-2);border-bottom:1px solid var(--color-border)}.lc-diff__stat{font-size:.6875rem;font-weight:600}.lc-diff__stat--add{color:var(--color-on-success-subtle)}.lc-diff__stat--del{color:var(--color-on-error-subtle)}.lc-diff__headers,.lc-diff__pane-header{padding:4px 12px;background:var(--color-surface-2);font-size:.6875rem;font-weight:600;color:var(--color-text-secondary);border-bottom:1px solid var(--color-border)}.lc-diff__body{overflow-x:auto}.lc-diff__side-by-side{display:grid;grid-template-columns:1fr 1fr}.lc-diff__pane{overflow-x:auto;min-width:0}.lc-diff__pane--left{border-right:1px solid var(--color-border)}.lc-diff__line{display:flex;min-height:1.5em;padding:0 8px}.lc-diff__line--added{background:var(--color-success-subtle)}.lc-diff__line--removed{background:var(--color-error-subtle)}.lc-diff__line--empty{background:var(--color-surface-2)}.lc-diff__num{flex-shrink:0;width:36px;text-align:right;padding-right:8px;color:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.lc-diff__num--left,.lc-diff__num--right{width:36px}.lc-diff__prefix{flex-shrink:0;width:16px;text-align:center;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none;font-weight:600}.lc-diff__line--added .lc-diff__prefix{color:var(--color-on-success-subtle)}.lc-diff__line--removed .lc-diff__prefix{color:var(--color-on-error-subtle)}.lc-diff__content{flex:1;white-space:pre;color:var(--color-text-primary)}.lc-diff__file-label{color:var(--color-text-secondary)}.lc-diff__too-large{margin:0;padding:12px;font-family:var(--font-family-sans, sans-serif);font-size:.8125rem;color:var(--color-text-secondary);background:var(--color-surface-2)}\n"]
|
|
285
285
|
}]
|
|
286
286
|
}],
|
|
287
287
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-diff-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/diff-viewer/diff-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/diff-viewer/diff-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\n\nexport type DiffViewMode = 'side-by-side' | 'inline';\n\ninterface DiffLine {\n type: 'unchanged' | 'added' | 'removed';\n leftNum: number | null;\n rightNum: number | null;\n content: string;\n}\n\n/** Edit-script op produced by the line diff (before numbering). */\ntype DiffOp = readonly [type: DiffLine['type'], lines: readonly string[]];\n\n/**\n * Line diff: common prefix/suffix are peeled off cheaply, the remainder goes\n * through Myers' O((N+M)·D) \"bisect\" (find where the forward and reverse\n * D-paths meet, split there, recurse) — linear space, no N×M matrix. Two\n * 5 000-line inputs used to allocate ~25M numbers and freeze the tab.\n */\nfunction diffLines(a: readonly string[], b: readonly string[]): DiffOp[] {\n if (a.length === 0 && b.length === 0) return [];\n if (a.length === 0) return [['added', b]];\n if (b.length === 0) return [['removed', a]];\n\n // Common prefix / suffix.\n let start = 0;\n const maxPrefix = Math.min(a.length, b.length);\n while (start < maxPrefix && a[start] === b[start]) start++;\n let end = 0;\n const maxSuffix = maxPrefix - start;\n while (end < maxSuffix && a[a.length - 1 - end] === b[b.length - 1 - end]) end++;\n\n const ops: DiffOp[] = [];\n if (start > 0) ops.push(['unchanged', a.slice(0, start)]);\n const midA = a.slice(start, a.length - end);\n const midB = b.slice(start, b.length - end);\n if (midA.length && midB.length) {\n // Disjoint inputs are Myers' worst case (D = N + M); answer them in linear time.\n if (sharesLine(midA, midB)) ops.push(...bisect(midA, midB));\n else ops.push(['removed', midA], ['added', midB]);\n } else if (midA.length) ops.push(['removed', midA]);\n else if (midB.length) ops.push(['added', midB]);\n if (end > 0) ops.push(['unchanged', a.slice(a.length - end)]);\n return ops;\n}\n\nfunction sharesLine(a: readonly string[], b: readonly string[]): boolean {\n const seen = new Set(a);\n return b.some(line => seen.has(line));\n}\n\n/** Myers middle-snake bisection (as in diff-match-patch), on line arrays. */\nfunction bisect(a: readonly string[], b: readonly string[]): DiffOp[] {\n const n = a.length;\n const m = b.length;\n const maxD = Math.ceil((n + m) / 2);\n const offset = maxD;\n const size = 2 * maxD;\n const v1 = new Int32Array(size).fill(-1);\n const v2 = new Int32Array(size).fill(-1);\n v1[offset + 1] = 0;\n v2[offset + 1] = 0;\n const delta = n - m;\n // With an odd delta the paths can only overlap on the forward pass.\n const front = delta % 2 !== 0;\n let k1start = 0, k1end = 0, k2start = 0, k2end = 0;\n\n for (let d = 0; d < maxD; d++) {\n // Forward path.\n for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {\n const k1o = offset + k1;\n let x1 = k1 === -d || (k1 !== d && v1[k1o - 1] < v1[k1o + 1]) ? v1[k1o + 1] : v1[k1o - 1] + 1;\n let y1 = x1 - k1;\n while (x1 < n && y1 < m && a[x1] === b[y1]) { x1++; y1++; }\n v1[k1o] = x1;\n if (x1 > n) k1end += 2;\n else if (y1 > m) k1start += 2;\n else if (front) {\n const k2o = offset + delta - k1;\n if (k2o >= 0 && k2o < size && v2[k2o] !== -1 && x1 >= n - v2[k2o]) {\n return splitAt(a, b, x1, y1);\n }\n }\n }\n // Reverse path.\n for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {\n const k2o = offset + k2;\n let x2 = k2 === -d || (k2 !== d && v2[k2o - 1] < v2[k2o + 1]) ? v2[k2o + 1] : v2[k2o - 1] + 1;\n let y2 = x2 - k2;\n while (x2 < n && y2 < m && a[n - x2 - 1] === b[m - y2 - 1]) { x2++; y2++; }\n v2[k2o] = x2;\n if (x2 > n) k2end += 2;\n else if (y2 > m) k2start += 2;\n else if (!front) {\n const k1o = offset + delta - k2;\n if (k1o >= 0 && k1o < size && v1[k1o] !== -1) {\n const x1 = v1[k1o];\n if (x1 >= n - x2) return splitAt(a, b, x1, offset + x1 - k1o);\n }\n }\n }\n }\n // Nothing in common at all.\n return [['removed', a], ['added', b]];\n}\n\nfunction splitAt(a: readonly string[], b: readonly string[], x: number, y: number): DiffOp[] {\n return [...diffLines(a.slice(0, x), b.slice(0, y)), ...diffLines(a.slice(x), b.slice(y))];\n}\n\n@Component({\n selector: 'lc-diff-viewer',\n standalone: true,\n templateUrl: './diff-viewer.component.html',\n styleUrls: ['./diff-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DiffViewerComponent {\n readonly oldText = input.required<string>();\n readonly newText = input.required<string>();\n readonly oldLabel = input('Original');\n readonly newLabel = input('Modified');\n readonly mode = input<DiffViewMode>('side-by-side');\n readonly showLineNumbers = input(true);\n readonly contextLines = input(Infinity);\n\n /**\n * Upper bound (per side, in lines) above which no diff is computed and a\n * \"too large\" notice is rendered instead — a UI diff of that size is neither\n * readable nor cheap. Raise it deliberately when you know the inputs.\n */\n readonly maxLines = input(20_000);\n\n private readonly oldLines = computed(() => this.oldText().split('\\n'));\n private readonly newLines = computed(() => this.newText().split('\\n'));\n protected readonly oldLineCount = computed(() => this.oldLines().length);\n protected readonly newLineCount = computed(() => this.newLines().length);\n\n /** True when either side exceeds `maxLines`; the diff is then skipped. */\n protected readonly tooLarge = computed(\n () => Math.max(this.oldLines().length, this.newLines().length) > this.maxLines(),\n );\n\n protected readonly diffLines = computed<DiffLine[]>(() => {\n if (this.tooLarge()) return [];\n return this.numberOps(diffLines(this.oldLines(), this.newLines()));\n });\n\n protected readonly filteredLines = computed(() => {\n const ctx = this.contextLines();\n const lines = this.diffLines();\n if (ctx === Infinity) return lines;\n\n const changed = new Set<number>();\n lines.forEach((l, i) => { if (l.type !== 'unchanged') changed.add(i); });\n\n const visible = new Set<number>();\n for (const idx of changed) {\n for (let j = Math.max(0, idx - ctx); j <= Math.min(lines.length - 1, idx + ctx); j++) {\n visible.add(j);\n }\n }\n\n return lines.filter((_, i) => visible.has(i));\n });\n\n protected readonly sideBySide = computed(() => {\n if (this.mode() !== 'side-by-side') return { left: [], right: [] };\n\n const lines = this.filteredLines();\n const left: { num: number | null; content: string; type: string }[] = [];\n const right: { num: number | null; content: string; type: string }[] = [];\n\n for (const line of lines) {\n if (line.type === 'unchanged') {\n left.push({ num: line.leftNum, content: line.content, type: 'unchanged' });\n right.push({ num: line.rightNum, content: line.content, type: 'unchanged' });\n } else if (line.type === 'removed') {\n left.push({ num: line.leftNum, content: line.content, type: 'removed' });\n right.push({ num: null, content: '', type: 'empty' });\n } else {\n left.push({ num: null, content: '', type: 'empty' });\n right.push({ num: line.rightNum, content: line.content, type: 'added' });\n }\n }\n\n return { left, right };\n });\n\n protected readonly stats = computed(() => {\n let additions = 0;\n let deletions = 0;\n for (const l of this.diffLines()) {\n if (l.type === 'added') additions++;\n else if (l.type === 'removed') deletions++;\n }\n return { additions, deletions };\n });\n\n /** Expand the edit script into numbered lines (removed before added). */\n private numberOps(ops: readonly DiffOp[]): DiffLine[] {\n const result: DiffLine[] = [];\n let i = 0, j = 0;\n for (const [type, lines] of ops) {\n for (const content of lines) {\n if (type === 'unchanged') result.push({ type, leftNum: ++i, rightNum: ++j, content });\n else if (type === 'removed') result.push({ type, leftNum: ++i, rightNum: null, content });\n else result.push({ type, leftNum: null, rightNum: ++j, content });\n }\n }\n return result;\n }\n}\n","<div class=\"lc-diff\">\n <!-- Stats bar -->\n <div class=\"lc-diff__stats\">\n <span class=\"lc-diff__stat lc-diff__stat--add\">+{{ stats().additions }}</span>\n <span class=\"lc-diff__stat lc-diff__stat--del\">−{{ stats().deletions }}</span>\n </div>\n\n @if (tooLarge()) {\n <!-- Size cap hit: no diff is computed -->\n <p class=\"lc-diff__too-large\" role=\"status\">\n Diff too large to display ({{ oldLabel() }}: {{ oldLineCount() }} lines,\n {{ newLabel() }}: {{ newLineCount() }} lines; limit {{ maxLines() }} lines per side).\n </p>\n } @else if (mode() === 'inline') {\n <!-- Inline mode -->\n <div class=\"lc-diff__inline\">\n <div class=\"lc-diff__headers\">\n <span class=\"lc-diff__file-label\">{{ oldLabel() }} → {{ newLabel() }}</span>\n </div>\n <div class=\"lc-diff__body\">\n @for (line of filteredLines(); track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num lc-diff__num--left\">{{ line.leftNum ?? '' }}</span>\n <span class=\"lc-diff__num lc-diff__num--right\">{{ line.rightNum ?? '' }}</span>\n }\n <span class=\"lc-diff__prefix\">{{ line.type === 'added' ? '+' : line.type === 'removed' ? '−' : ' ' }}</span>\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- Side-by-side mode -->\n <div class=\"lc-diff__side-by-side\">\n <div class=\"lc-diff__pane lc-diff__pane--left\">\n <div class=\"lc-diff__pane-header\">{{ oldLabel() }}</div>\n @for (line of sideBySide().left; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n <div class=\"lc-diff__pane lc-diff__pane--right\">\n <div class=\"lc-diff__pane-header\">{{ newLabel() }}</div>\n @for (line of sideBySide().right; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-diff-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/diff-viewer/diff-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/diff-viewer/diff-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\n\nexport type DiffViewMode = 'side-by-side' | 'inline';\n\ninterface DiffLine {\n type: 'unchanged' | 'added' | 'removed';\n leftNum: number | null;\n rightNum: number | null;\n content: string;\n}\n\n/** Edit-script op produced by the line diff (before numbering). */\ntype DiffOp = readonly [type: DiffLine['type'], lines: readonly string[]];\n\n/**\n * Line diff: common prefix/suffix are peeled off cheaply, the remainder goes\n * through Myers' O((N+M)·D) \"bisect\" (find where the forward and reverse\n * D-paths meet, split there, recurse) — linear space, no N×M matrix. Two\n * 5 000-line inputs used to allocate ~25M numbers and freeze the tab.\n */\nfunction diffLines(a: readonly string[], b: readonly string[]): DiffOp[] {\n if (a.length === 0 && b.length === 0) return [];\n if (a.length === 0) return [['added', b]];\n if (b.length === 0) return [['removed', a]];\n\n // Common prefix / suffix.\n let start = 0;\n const maxPrefix = Math.min(a.length, b.length);\n while (start < maxPrefix && a[start] === b[start]) start++;\n let end = 0;\n const maxSuffix = maxPrefix - start;\n while (end < maxSuffix && a[a.length - 1 - end] === b[b.length - 1 - end]) end++;\n\n const ops: DiffOp[] = [];\n if (start > 0) ops.push(['unchanged', a.slice(0, start)]);\n const midA = a.slice(start, a.length - end);\n const midB = b.slice(start, b.length - end);\n if (midA.length && midB.length) {\n // Disjoint inputs are Myers' worst case (D = N + M); answer them in linear time.\n if (sharesLine(midA, midB)) ops.push(...bisect(midA, midB));\n else ops.push(['removed', midA], ['added', midB]);\n } else if (midA.length) ops.push(['removed', midA]);\n else if (midB.length) ops.push(['added', midB]);\n if (end > 0) ops.push(['unchanged', a.slice(a.length - end)]);\n return ops;\n}\n\nfunction sharesLine(a: readonly string[], b: readonly string[]): boolean {\n const seen = new Set(a);\n return b.some(line => seen.has(line));\n}\n\n/** Myers middle-snake bisection (as in diff-match-patch), on line arrays. */\nfunction bisect(a: readonly string[], b: readonly string[]): DiffOp[] {\n const n = a.length;\n const m = b.length;\n const maxD = Math.ceil((n + m) / 2);\n const offset = maxD;\n const size = 2 * maxD;\n const v1 = new Int32Array(size).fill(-1);\n const v2 = new Int32Array(size).fill(-1);\n v1[offset + 1] = 0;\n v2[offset + 1] = 0;\n const delta = n - m;\n // With an odd delta the paths can only overlap on the forward pass.\n const front = delta % 2 !== 0;\n let k1start = 0, k1end = 0, k2start = 0, k2end = 0;\n\n for (let d = 0; d < maxD; d++) {\n // Forward path.\n for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {\n const k1o = offset + k1;\n let x1 = k1 === -d || (k1 !== d && v1[k1o - 1] < v1[k1o + 1]) ? v1[k1o + 1] : v1[k1o - 1] + 1;\n let y1 = x1 - k1;\n while (x1 < n && y1 < m && a[x1] === b[y1]) { x1++; y1++; }\n v1[k1o] = x1;\n if (x1 > n) k1end += 2;\n else if (y1 > m) k1start += 2;\n else if (front) {\n const k2o = offset + delta - k1;\n if (k2o >= 0 && k2o < size && v2[k2o] !== -1 && x1 >= n - v2[k2o]) {\n return splitAt(a, b, x1, y1);\n }\n }\n }\n // Reverse path.\n for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {\n const k2o = offset + k2;\n let x2 = k2 === -d || (k2 !== d && v2[k2o - 1] < v2[k2o + 1]) ? v2[k2o + 1] : v2[k2o - 1] + 1;\n let y2 = x2 - k2;\n while (x2 < n && y2 < m && a[n - x2 - 1] === b[m - y2 - 1]) { x2++; y2++; }\n v2[k2o] = x2;\n if (x2 > n) k2end += 2;\n else if (y2 > m) k2start += 2;\n else if (!front) {\n const k1o = offset + delta - k2;\n if (k1o >= 0 && k1o < size && v1[k1o] !== -1) {\n const x1 = v1[k1o];\n if (x1 >= n - x2) return splitAt(a, b, x1, offset + x1 - k1o);\n }\n }\n }\n }\n // Nothing in common at all.\n return [['removed', a], ['added', b]];\n}\n\nfunction splitAt(a: readonly string[], b: readonly string[], x: number, y: number): DiffOp[] {\n return [...diffLines(a.slice(0, x), b.slice(0, y)), ...diffLines(a.slice(x), b.slice(y))];\n}\n\n@Component({\n selector: 'lc-diff-viewer',\n standalone: true,\n templateUrl: './diff-viewer.component.html',\n styleUrls: ['./diff-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DiffViewerComponent {\n readonly oldText = input.required<string>();\n readonly newText = input.required<string>();\n readonly oldLabel = input('Original');\n readonly newLabel = input('Modified');\n readonly mode = input<DiffViewMode>('side-by-side');\n readonly showLineNumbers = input(true);\n readonly contextLines = input(Infinity);\n\n /**\n * Upper bound (per side, in lines) above which no diff is computed and a\n * \"too large\" notice is rendered instead — a UI diff of that size is neither\n * readable nor cheap. Raise it deliberately when you know the inputs.\n */\n readonly maxLines = input(20_000);\n\n private readonly oldLines = computed(() => this.oldText().split('\\n'));\n private readonly newLines = computed(() => this.newText().split('\\n'));\n protected readonly oldLineCount = computed(() => this.oldLines().length);\n protected readonly newLineCount = computed(() => this.newLines().length);\n\n /** True when either side exceeds `maxLines`; the diff is then skipped. */\n protected readonly tooLarge = computed(\n () => Math.max(this.oldLines().length, this.newLines().length) > this.maxLines(),\n );\n\n protected readonly diffLines = computed<DiffLine[]>(() => {\n if (this.tooLarge()) return [];\n return this.numberOps(diffLines(this.oldLines(), this.newLines()));\n });\n\n protected readonly filteredLines = computed(() => {\n const ctx = this.contextLines();\n const lines = this.diffLines();\n if (ctx === Infinity) return lines;\n\n const changed = new Set<number>();\n lines.forEach((l, i) => { if (l.type !== 'unchanged') changed.add(i); });\n\n const visible = new Set<number>();\n for (const idx of changed) {\n for (let j = Math.max(0, idx - ctx); j <= Math.min(lines.length - 1, idx + ctx); j++) {\n visible.add(j);\n }\n }\n\n return lines.filter((_, i) => visible.has(i));\n });\n\n protected readonly sideBySide = computed(() => {\n if (this.mode() !== 'side-by-side') return { left: [], right: [] };\n\n const lines = this.filteredLines();\n const left: { num: number | null; content: string; type: string }[] = [];\n const right: { num: number | null; content: string; type: string }[] = [];\n\n for (const line of lines) {\n if (line.type === 'unchanged') {\n left.push({ num: line.leftNum, content: line.content, type: 'unchanged' });\n right.push({ num: line.rightNum, content: line.content, type: 'unchanged' });\n } else if (line.type === 'removed') {\n left.push({ num: line.leftNum, content: line.content, type: 'removed' });\n right.push({ num: null, content: '', type: 'empty' });\n } else {\n left.push({ num: null, content: '', type: 'empty' });\n right.push({ num: line.rightNum, content: line.content, type: 'added' });\n }\n }\n\n return { left, right };\n });\n\n protected readonly stats = computed(() => {\n let additions = 0;\n let deletions = 0;\n for (const l of this.diffLines()) {\n if (l.type === 'added') additions++;\n else if (l.type === 'removed') deletions++;\n }\n return { additions, deletions };\n });\n\n /** Expand the edit script into numbered lines (removed before added). */\n private numberOps(ops: readonly DiffOp[]): DiffLine[] {\n const result: DiffLine[] = [];\n let i = 0, j = 0;\n for (const [type, lines] of ops) {\n for (const content of lines) {\n if (type === 'unchanged') result.push({ type, leftNum: ++i, rightNum: ++j, content });\n else if (type === 'removed') result.push({ type, leftNum: ++i, rightNum: null, content });\n else result.push({ type, leftNum: null, rightNum: ++j, content });\n }\n }\n return result;\n }\n}\n","<div class=\"lc-diff\">\n <!-- Stats bar -->\n <div class=\"lc-diff__stats\">\n <span class=\"lc-diff__stat lc-diff__stat--add\">+{{ stats().additions }}</span>\n <span class=\"lc-diff__stat lc-diff__stat--del\">−{{ stats().deletions }}</span>\n </div>\n\n @if (tooLarge()) {\n <!-- Size cap hit: no diff is computed -->\n <p class=\"lc-diff__too-large\" role=\"status\">\n Diff too large to display ({{ oldLabel() }}: {{ oldLineCount() }} lines,\n {{ newLabel() }}: {{ newLineCount() }} lines; limit {{ maxLines() }} lines per side).\n </p>\n } @else if (mode() === 'inline') {\n <!-- Inline mode -->\n <div class=\"lc-diff__inline\">\n <div class=\"lc-diff__headers\">\n <span class=\"lc-diff__file-label\">{{ oldLabel() }} → {{ newLabel() }}</span>\n </div>\n <div class=\"lc-diff__body\">\n @for (line of filteredLines(); track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num lc-diff__num--left\">{{ line.leftNum ?? '' }}</span>\n <span class=\"lc-diff__num lc-diff__num--right\">{{ line.rightNum ?? '' }}</span>\n }\n <span class=\"lc-diff__prefix\">{{ line.type === 'added' ? '+' : line.type === 'removed' ? '−' : ' ' }}</span>\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- Side-by-side mode -->\n <div class=\"lc-diff__side-by-side\">\n <div class=\"lc-diff__pane lc-diff__pane--left\">\n <div class=\"lc-diff__pane-header\">{{ oldLabel() }}</div>\n @for (line of sideBySide().left; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--removed]=\"line.type === 'removed'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n <div class=\"lc-diff__pane lc-diff__pane--right\">\n <div class=\"lc-diff__pane-header\">{{ newLabel() }}</div>\n @for (line of sideBySide().right; track $index) {\n <div\n class=\"lc-diff__line\"\n [class.lc-diff__line--added]=\"line.type === 'added'\"\n [class.lc-diff__line--empty]=\"line.type === 'empty'\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-diff__num\">{{ line.num ?? '' }}</span>\n }\n <span class=\"lc-diff__content\">{{ line.content }}</span>\n </div>\n }\n </div>\n </div>\n }\n</div>\n"],"mappings":";;AAyBA,SAAS,UAAU,GAAsB,GAAoB;CAC3D,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,OAAO,CAAA;CAC7C,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC;CACxC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC;CAG1C,IAAI,QAAQ;CACZ,MAAM,YAAY,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;CAC7C,OAAO,QAAQ,aAAa,EAAE,WAAW,EAAE,QAAQ;CACnD,IAAI,MAAM;CACV,MAAM,YAAY,YAAY;CAC9B,OAAO,MAAM,aAAa,EAAE,EAAE,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,IAAI,MAAM;CAE3E,MAAM,MAAgB,CAAA;CACtB,IAAI,QAAQ,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC;CACxD,MAAM,OAAO,EAAE,MAAM,OAAO,EAAE,SAAS,GAAG;CAC1C,MAAM,OAAO,EAAE,MAAM,OAAO,EAAE,SAAS,GAAG;CAC1C,IAAI,KAAK,UAAU,KAAK,QAAQ;EAE9B,IAAI,WAAW,MAAM,IAAI,GAAG,IAAI,KAAK,GAAG,OAAO,MAAM,IAAI,CAAC;OACrD,IAAI,KAAK,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC;CAClD,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,WAAW,IAAI,CAAC;MAC7C,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC;CAC9C,IAAI,MAAM,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,CAAC;CAC5D,OAAO;AACT;AAEA,SAAS,WAAW,GAAsB,GAAoB;CAC5D,MAAM,OAAO,IAAI,IAAI,CAAC;CACtB,OAAO,EAAE,MAAK,SAAQ,KAAK,IAAI,IAAI,CAAC;AACtC;AAGA,SAAS,OAAO,GAAsB,GAAoB;CACxD,MAAM,IAAI,EAAE;CACZ,MAAM,IAAI,EAAE;CACZ,MAAM,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC;CAClC,MAAM,SAAS;CACf,MAAM,OAAO,IAAI;CACjB,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC,CAAC,KAAK,EAAE;CACvC,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC,CAAC,KAAK,EAAE;CACvC,GAAG,SAAS,KAAK;CACjB,GAAG,SAAS,KAAK;CACjB,MAAM,QAAQ,IAAI;CAElB,MAAM,QAAQ,QAAQ,MAAM;CAC5B,IAAI,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ;CAEjD,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK;EAE7B,KAAK,IAAI,KAAK,CAAC,IAAI,SAAS,MAAM,IAAI,OAAO,MAAM,GAAG;GACpD,MAAM,MAAM,SAAS;GACrB,IAAI,KAAK,OAAO,CAAC,KAAM,OAAO,KAAK,GAAG,MAAM,KAAK,GAAG,MAAM,KAAM,GAAG,MAAM,KAAK,GAAG,MAAM,KAAK;GAC5F,IAAI,KAAK,KAAK;GACd,OAAO,KAAK,KAAK,KAAK,KAAK,EAAE,QAAQ,EAAE,KAAK;IAAE;IAAM;GAAM;GAC1D,GAAG,OAAO;GACV,IAAI,KAAK,GAAG,SAAS;QAChB,IAAI,KAAK,GAAG,WAAW;QACvB,IAAI,OAAO;IACd,MAAM,MAAM,SAAS,QAAQ;IAC7B,IAAI,OAAO,KAAK,MAAM,QAAQ,GAAG,SAAS,MAAM,MAAM,IAAI,GAAG,MAC3D,OAAO,QAAQ,GAAG,GAAG,IAAI,EAAE;GAE/B;EACF;EAEA,KAAK,IAAI,KAAK,CAAC,IAAI,SAAS,MAAM,IAAI,OAAO,MAAM,GAAG;GACpD,MAAM,MAAM,SAAS;GACrB,IAAI,KAAK,OAAO,CAAC,KAAM,OAAO,KAAK,GAAG,MAAM,KAAK,GAAG,MAAM,KAAM,GAAG,MAAM,KAAK,GAAG,MAAM,KAAK;GAC5F,IAAI,KAAK,KAAK;GACd,OAAO,KAAK,KAAK,KAAK,KAAK,EAAE,IAAI,KAAK,OAAO,EAAE,IAAI,KAAK,IAAI;IAAE;IAAM;GAAM;GAC1E,GAAG,OAAO;GACV,IAAI,KAAK,GAAG,SAAS;QAChB,IAAI,KAAK,GAAG,WAAW;QACvB,IAAI,CAAC,OAAO;IACf,MAAM,MAAM,SAAS,QAAQ;IAC7B,IAAI,OAAO,KAAK,MAAM,QAAQ,GAAG,SAAS,IAAI;KAC5C,MAAM,KAAK,GAAG;KACd,IAAI,MAAM,IAAI,IAAI,OAAO,QAAQ,GAAG,GAAG,IAAI,SAAS,KAAK,GAAG;IAC9D;GACF;EACF;CACF;CAEA,OAAO,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACtC;AAEA,SAAS,QAAQ,GAAsB,GAAsB,GAAW,GAAS;CAC/E,OAAO,CAAC,GAAG,UAAU,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,UAAU,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;AAC1F;AASA,IAAa,sBAAb,MAAa,oBAAmB;CACrB,UAAU,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,UAAU,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,WAAW,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,WAAW,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,OAAO,MAAoB,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAC1C,kBAAkB,MAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAC7B,eAAe,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAO9B,WAAW,MAAM,KAAO,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEhB,WAAW,eAAe,KAAK,QAAO,CAAE,CAAC,MAAM,IAAI,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACrD,WAAW,eAAe,KAAK,QAAO,CAAE,CAAC,MAAM,IAAI,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACnD,eAAe,eAAe,KAAK,SAAQ,CAAE,CAAC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CACrD,eAAe,eAAe,KAAK,SAAQ,CAAE,CAAC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAGrD,WAAW,eACtB,KAAK,IAAI,KAAK,SAAQ,CAAE,CAAC,QAAQ,KAAK,SAAQ,CAAE,CAAC,MAAM,IAAI,KAAK,SAAQ,GAC/E,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEkB,YAAY,eAA0B;EACvD,IAAI,KAAK,SAAQ,GAAI,OAAO,CAAA;EAC5B,OAAO,KAAK,UAAU,UAAU,KAAK,SAAQ,GAAI,KAAK,SAAQ,CAAE,CAAC;CACnE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,gBAAgB,eAAc;EAC/C,MAAM,MAAM,KAAK,aAAY;EAC7B,MAAM,QAAQ,KAAK,UAAS;EAC5B,IAAI,QAAQ,UAAU,OAAO;EAE7B,MAAM,0BAAU,IAAI,IAAG;EACvB,MAAM,SAAS,GAAG,MAAK;GAAG,IAAI,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;EAAG,CAAC;EAEvE,MAAM,0BAAU,IAAI,IAAG;EACvB,KAAK,MAAM,OAAO,SAChB,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,GAAG,GAAG,KAAK,KAAK,IAAI,MAAM,SAAS,GAAG,MAAM,GAAG,GAAG,KAC/E,QAAQ,IAAI,CAAC;EAIjB,OAAO,MAAM,QAAQ,GAAG,MAAM,QAAQ,IAAI,CAAC,CAAC;CAC9C,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,aAAa,eAAc;EAC5C,IAAI,KAAK,KAAI,MAAO,gBAAgB,OAAO;GAAE,MAAM,CAAA;GAAI,OAAO,CAAA;EAAE;EAEhE,MAAM,QAAQ,KAAK,cAAa;EAChC,MAAM,OAAgE,CAAA;EACtE,MAAM,QAAiE,CAAA;EAEvE,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,aAAa;GAC7B,KAAK,KAAK;IAAE,KAAK,KAAK;IAAS,SAAS,KAAK;IAAS,MAAM;GAAW,CAAE;GACzE,MAAM,KAAK;IAAE,KAAK,KAAK;IAAU,SAAS,KAAK;IAAS,MAAM;GAAW,CAAE;EAC7E,OAAO,IAAI,KAAK,SAAS,WAAW;GAClC,KAAK,KAAK;IAAE,KAAK,KAAK;IAAS,SAAS,KAAK;IAAS,MAAM;GAAS,CAAE;GACvE,MAAM,KAAK;IAAE,KAAK;IAAM,SAAS;IAAI,MAAM;GAAO,CAAE;EACtD,OAAO;GACL,KAAK,KAAK;IAAE,KAAK;IAAM,SAAS;IAAI,MAAM;GAAO,CAAE;GACnD,MAAM,KAAK;IAAE,KAAK,KAAK;IAAU,SAAS,KAAK;IAAS,MAAM;GAAO,CAAE;EACzE;EAGF,OAAO;GAAE;GAAM;EAAK;CACtB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,QAAQ,eAAc;EACvC,IAAI,YAAY;EAChB,IAAI,YAAY;EAChB,KAAK,MAAM,KAAK,KAAK,UAAS,GAC5B,IAAI,EAAE,SAAS,SAAS;OACnB,IAAI,EAAE,SAAS,WAAW;EAEjC,OAAO;GAAE;GAAW;EAAS;CAC/B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGM,UAAU,KAAsB;EACtC,MAAM,SAAqB,CAAA;EAC3B,IAAI,IAAI,GAAG,IAAI;EACf,KAAK,MAAM,CAAC,MAAM,UAAU,KAC1B,KAAK,MAAM,WAAW,OACpB,IAAI,SAAS,aAAa,OAAO,KAAK;GAAE;GAAM,SAAS,EAAE;GAAG,UAAU,EAAE;GAAG;EAAO,CAAE;OAC/E,IAAI,SAAS,WAAW,OAAO,KAAK;GAAE;GAAM,SAAS,EAAE;GAAG,UAAU;GAAM;EAAO,CAAE;OACnF,OAAO,KAAK;GAAE;GAAM,SAAS;GAAM,UAAU,EAAE;GAAG;EAAO,CAAE;EAGpE,OAAO;CACT;CA9FW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAmB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UC3HhC;EAwEA,QAAA,CAAA,g4DAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;ADmDa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAP/B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAgB,YACd;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,g4DAAA;EAAA,CAAA"}
|
|
@@ -509,7 +509,7 @@ var LogViewerComponent = class LogViewerComponent {
|
|
|
509
509
|
}],
|
|
510
510
|
ngImport: i0,
|
|
511
511
|
template: "<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n",
|
|
512
|
-
styles: [".lc-log-viewer{--lc-log-viewer-line-height: 22px;position:relative;display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--border-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-surface);color:var(--color-text-primary)}.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(--border-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:box-shadow .15s}.lc-log-viewer__level-pill--active{box-shadow:inset 0 0 0 1px currentColor}.lc-log-viewer__level-pill--error{background-color:#9d0e0e2e;color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#e1a0402e;color:var(--color-on-warning-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}@media(forced-colors:active){.lc-log-viewer__level-pill--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.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__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.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:var(--lc-log-viewer-line-height);white-space:nowrap;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--clickable{cursor:pointer}.lc-log-viewer__line--clickable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary-500) 60%,transparent)}.lc-log-viewer__line--debug{opacity:.6}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#e1a0401f}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#9d0e0e1f}.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:var(--color-text-tertiary)}.lc-log-viewer__line--info .lc-log-viewer__level{color:var(--color-on-info-subtle)}.lc-log-viewer__line--warn .lc-log-viewer__level{color:var(--color-on-warning-subtle)}.lc-log-viewer__line--error .lc-log-viewer__level{color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__level{color:#9399b2}.lc-log-viewer--terminal .lc-log-viewer__line--info .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer--terminal .lc-log-viewer__line--warn .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer--terminal .lc-log-viewer__line--error .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:color-mix(in srgb,var(--color-warning-default) 45%,transparent);color:inherit;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-surface);color:var(--color-text-primary);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:var(--elevation-1);z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-surface-hover)}.lc-log-viewer__jump-bottom:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.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:#45475a}.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}:root.light .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.light .lc-log-viewer--log .ansi-red{color:#d20f39}:root.light .lc-log-viewer--log .ansi-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.light .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.light .lc-log-viewer--log .ansi-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.light .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.light .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.light .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.light .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.light .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}@media(prefers-color-scheme:light){:root.system:not(.dark) .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.system:not(.dark) .lc-log-viewer--log .ansi-red{color:#d20f39}:root.system:not(.dark) .lc-log-viewer--log .ansi-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.system:not(.dark) .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.system:not(.dark) .lc-log-viewer--log .ansi-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.system:not(.dark) .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}}.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"],
|
|
512
|
+
styles: [".lc-log-viewer{--lc-log-viewer-line-height: 22px;position:relative;display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--border-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-surface);color:var(--color-text-primary)}.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(--border-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{position:relative;font-size:.6875rem;padding:.125rem .5rem;border-radius:9999px;border:none;cursor:pointer;font-family:var(--font-family-sans);transition:box-shadow .15s}.lc-log-viewer__level-pill:before{content:\"\";position:absolute;inset-inline:-.125rem;top:calc(50% - 20px);bottom:calc(50% - 20px)}.lc-log-viewer__level-pill--active{box-shadow:inset 0 0 0 1px currentColor}.lc-log-viewer__level-pill--error{background-color:#9d0e0e2e;color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#e1a0402e;color:var(--color-on-warning-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}@media(forced-colors:active){.lc-log-viewer__level-pill--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-log-viewer__line-count{font-size:.6875rem;color:var(--color-text-tertiary);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{color:var(--color-text-tertiary);opacity:1}.lc-log-viewer__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.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:var(--lc-log-viewer-line-height);white-space:nowrap;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--clickable{cursor:pointer}.lc-log-viewer__line--clickable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary-500) 60%,transparent)}.lc-log-viewer__line--debug{color:var(--color-text-secondary)}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#e1a0401f}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#9d0e0e1f}.lc-log-viewer--terminal .lc-log-viewer__line--error{background-color:#f38ba814}.lc-log-viewer__line-num{color:var(--color-text-secondary);min-width:3ch;text-align:right;-webkit-user-select:none;user-select:none}.lc-log-viewer__timestamp{color:var(--color-text-secondary);min-width:12ch}.lc-log-viewer__level{min-width:5ch;font-weight:600}.lc-log-viewer__line--debug .lc-log-viewer__level{color:var(--color-text-tertiary)}.lc-log-viewer__line--info .lc-log-viewer__level{color:var(--color-on-info-subtle)}.lc-log-viewer__line--warn .lc-log-viewer__level{color:var(--color-on-warning-subtle)}.lc-log-viewer__line--error .lc-log-viewer__level{color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__level{color:#9399b2}.lc-log-viewer--terminal .lc-log-viewer__line--info .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer--terminal .lc-log-viewer__line--warn .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer--terminal .lc-log-viewer__line--error .lc-log-viewer__level{color:#f38ba8}.lc-log-viewer__source{color:var(--color-text-secondary)}.lc-log-viewer__line--debug .lc-log-viewer__line-num,.lc-log-viewer__line--debug .lc-log-viewer__timestamp,.lc-log-viewer__line--debug .lc-log-viewer__source{color:var(--color-text-tertiary)}.lc-log-viewer--terminal .lc-log-viewer__line-num,.lc-log-viewer--terminal .lc-log-viewer__timestamp,.lc-log-viewer--terminal .lc-log-viewer__source,.lc-log-viewer--terminal .lc-log-viewer__line--debug{color:#a6adc8}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__line-num,.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__timestamp,.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__source,.lc-log-viewer--terminal .lc-log-viewer__line-count,.lc-log-viewer--terminal .lc-log-viewer__search-input::placeholder{color:#9399b2}.lc-log-viewer__text{flex:1;min-width:0}.lc-log-viewer__match{background-color:color-mix(in srgb,var(--color-warning-default) 45%,transparent);color:inherit;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-surface);color:var(--color-text-primary);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:var(--elevation-1);z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-surface-hover)}.lc-log-viewer__jump-bottom:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.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:#45475a}.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}:root.light .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.light .lc-log-viewer--log .ansi-red{color:#d20f39}:root.light .lc-log-viewer--log .ansi-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.light .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.light .lc-log-viewer--log .ansi-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.light .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.light .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.light .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.light .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.light .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}@media(prefers-color-scheme:light){:root.system:not(.dark) .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.system:not(.dark) .lc-log-viewer--log .ansi-red{color:#d20f39}:root.system:not(.dark) .lc-log-viewer--log .ansi-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.system:not(.dark) .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.system:not(.dark) .lc-log-viewer--log .ansi-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.system:not(.dark) .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}}.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"],
|
|
513
513
|
dependencies: [{
|
|
514
514
|
kind: "component",
|
|
515
515
|
type: IconComponent,
|
|
@@ -558,7 +558,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
558
558
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
559
559
|
encapsulation: ViewEncapsulation.None,
|
|
560
560
|
template: "<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n",
|
|
561
|
-
styles: [".lc-log-viewer{--lc-log-viewer-line-height: 22px;position:relative;display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--border-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-surface);color:var(--color-text-primary)}.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(--border-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:box-shadow .15s}.lc-log-viewer__level-pill--active{box-shadow:inset 0 0 0 1px currentColor}.lc-log-viewer__level-pill--error{background-color:#9d0e0e2e;color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#e1a0402e;color:var(--color-on-warning-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}@media(forced-colors:active){.lc-log-viewer__level-pill--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.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__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.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:var(--lc-log-viewer-line-height);white-space:nowrap;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--clickable{cursor:pointer}.lc-log-viewer__line--clickable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary-500) 60%,transparent)}.lc-log-viewer__line--debug{opacity:.6}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#e1a0401f}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#9d0e0e1f}.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:var(--color-text-tertiary)}.lc-log-viewer__line--info .lc-log-viewer__level{color:var(--color-on-info-subtle)}.lc-log-viewer__line--warn .lc-log-viewer__level{color:var(--color-on-warning-subtle)}.lc-log-viewer__line--error .lc-log-viewer__level{color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__level{color:#9399b2}.lc-log-viewer--terminal .lc-log-viewer__line--info .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer--terminal .lc-log-viewer__line--warn .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer--terminal .lc-log-viewer__line--error .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:color-mix(in srgb,var(--color-warning-default) 45%,transparent);color:inherit;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-surface);color:var(--color-text-primary);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:var(--elevation-1);z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-surface-hover)}.lc-log-viewer__jump-bottom:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.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:#45475a}.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}:root.light .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.light .lc-log-viewer--log .ansi-red{color:#d20f39}:root.light .lc-log-viewer--log .ansi-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.light .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.light .lc-log-viewer--log .ansi-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.light .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.light .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.light .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.light .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.light .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}@media(prefers-color-scheme:light){:root.system:not(.dark) .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.system:not(.dark) .lc-log-viewer--log .ansi-red{color:#d20f39}:root.system:not(.dark) .lc-log-viewer--log .ansi-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.system:not(.dark) .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.system:not(.dark) .lc-log-viewer--log .ansi-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.system:not(.dark) .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}}.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"]
|
|
561
|
+
styles: [".lc-log-viewer{--lc-log-viewer-line-height: 22px;position:relative;display:flex;flex-direction:column;border:1px solid var(--color-divider, #e5e7eb);border-radius:var(--border-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-surface);color:var(--color-text-primary)}.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(--border-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{position:relative;font-size:.6875rem;padding:.125rem .5rem;border-radius:9999px;border:none;cursor:pointer;font-family:var(--font-family-sans);transition:box-shadow .15s}.lc-log-viewer__level-pill:before{content:\"\";position:absolute;inset-inline:-.125rem;top:calc(50% - 20px);bottom:calc(50% - 20px)}.lc-log-viewer__level-pill--active{box-shadow:inset 0 0 0 1px currentColor}.lc-log-viewer__level-pill--error{background-color:#9d0e0e2e;color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--error{background-color:#45202a;color:#f38ba8}.lc-log-viewer__level-pill--warn{background-color:#e1a0402e;color:var(--color-on-warning-subtle)}.lc-log-viewer--terminal .lc-log-viewer__level-pill--warn{background-color:#3d3520;color:#f9e2af}@media(forced-colors:active){.lc-log-viewer__level-pill--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-log-viewer__line-count{font-size:.6875rem;color:var(--color-text-tertiary);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{color:var(--color-text-tertiary);opacity:1}.lc-log-viewer__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.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:var(--lc-log-viewer-line-height);white-space:nowrap;transition:background-color .1s}.lc-log-viewer__line:hover{background-color:#80808014}.lc-log-viewer__line--clickable{cursor:pointer}.lc-log-viewer__line--clickable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary-500) 60%,transparent)}.lc-log-viewer__line--debug{color:var(--color-text-secondary)}.lc-log-viewer--log .lc-log-viewer__line--warn{background-color:#e1a0401f}.lc-log-viewer--terminal .lc-log-viewer__line--warn{background-color:#f9e2af0d}.lc-log-viewer--log .lc-log-viewer__line--error{background-color:#9d0e0e1f}.lc-log-viewer--terminal .lc-log-viewer__line--error{background-color:#f38ba814}.lc-log-viewer__line-num{color:var(--color-text-secondary);min-width:3ch;text-align:right;-webkit-user-select:none;user-select:none}.lc-log-viewer__timestamp{color:var(--color-text-secondary);min-width:12ch}.lc-log-viewer__level{min-width:5ch;font-weight:600}.lc-log-viewer__line--debug .lc-log-viewer__level{color:var(--color-text-tertiary)}.lc-log-viewer__line--info .lc-log-viewer__level{color:var(--color-on-info-subtle)}.lc-log-viewer__line--warn .lc-log-viewer__level{color:var(--color-on-warning-subtle)}.lc-log-viewer__line--error .lc-log-viewer__level{color:var(--color-on-error-subtle)}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__level{color:#9399b2}.lc-log-viewer--terminal .lc-log-viewer__line--info .lc-log-viewer__level{color:#89b4fa}.lc-log-viewer--terminal .lc-log-viewer__line--warn .lc-log-viewer__level{color:#f9e2af}.lc-log-viewer--terminal .lc-log-viewer__line--error .lc-log-viewer__level{color:#f38ba8}.lc-log-viewer__source{color:var(--color-text-secondary)}.lc-log-viewer__line--debug .lc-log-viewer__line-num,.lc-log-viewer__line--debug .lc-log-viewer__timestamp,.lc-log-viewer__line--debug .lc-log-viewer__source{color:var(--color-text-tertiary)}.lc-log-viewer--terminal .lc-log-viewer__line-num,.lc-log-viewer--terminal .lc-log-viewer__timestamp,.lc-log-viewer--terminal .lc-log-viewer__source,.lc-log-viewer--terminal .lc-log-viewer__line--debug{color:#a6adc8}.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__line-num,.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__timestamp,.lc-log-viewer--terminal .lc-log-viewer__line--debug .lc-log-viewer__source,.lc-log-viewer--terminal .lc-log-viewer__line-count,.lc-log-viewer--terminal .lc-log-viewer__search-input::placeholder{color:#9399b2}.lc-log-viewer__text{flex:1;min-width:0}.lc-log-viewer__match{background-color:color-mix(in srgb,var(--color-warning-default) 45%,transparent);color:inherit;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-surface);color:var(--color-text-primary);font-size:.75rem;font-family:var(--font-family-sans);cursor:pointer;box-shadow:var(--elevation-1);z-index:10;transition:background-color .15s}.lc-log-viewer__jump-bottom:hover{background-color:var(--color-surface-hover)}.lc-log-viewer__jump-bottom:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.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:#45475a}.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}:root.light .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.light .lc-log-viewer--log .ansi-red{color:#d20f39}:root.light .lc-log-viewer--log .ansi-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.light .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.light .lc-log-viewer--log .ansi-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.light .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.light .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.light .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.light .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.light .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.light .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.light .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.light .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}@media(prefers-color-scheme:light){:root.system:not(.dark) .lc-log-viewer--log .ansi-black{color:#4c4f69}:root.system:not(.dark) .lc-log-viewer--log .ansi-red{color:#d20f39}:root.system:not(.dark) .lc-log-viewer--log .ansi-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-blue{color:#1e66f5}:root.system:not(.dark) .lc-log-viewer--log .ansi-magenta{color:#8839ef}:root.system:not(.dark) .lc-log-viewer--log .ansi-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-white{color:#6c6f85}:root.system:not(.dark) .lc-log-viewer--log .ansi-gray{color:#9ca0b0}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-red{color:#e64553}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-green{color:#40a02b}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-yellow{color:#df8e1d}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-blue{color:#04a5e5}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-magenta{color:#ea76cb}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-cyan{color:#179299}:root.system:not(.dark) .lc-log-viewer--log .ansi-bright-white{color:#5c5f77}}.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"]
|
|
562
562
|
}]
|
|
563
563
|
}],
|
|
564
564
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-log-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n DestroyRef,\n ElementRef,\n Injector,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_ARROW_DOWN,\n ICON_CLIPBOARD,\n ICON_PLAYER_PAUSE,\n ICON_PLAYER_PLAY,\n ICON_SEARCH,\n ICON_TRASH,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\nexport type LogViewerVariant = 'terminal' | 'log';\n\nexport interface LogLine {\n text: string;\n level?: LogLevel;\n timestamp?: Date;\n source?: string;\n meta?: Record<string, unknown>;\n}\n\n/** SGR code → CSS class for the ANSI subset the viewer renders. */\nconst ANSI_CLASSES: Record<string, string> = {\n '1': 'ansi-bold',\n '3': 'ansi-italic',\n '30': 'ansi-black',\n '31': 'ansi-red',\n '32': 'ansi-green',\n '33': 'ansi-yellow',\n '34': 'ansi-blue',\n '35': 'ansi-magenta',\n '36': 'ansi-cyan',\n '37': 'ansi-white',\n '90': 'ansi-gray',\n '91': 'ansi-bright-red',\n '92': 'ansi-bright-green',\n '93': 'ansi-bright-yellow',\n '94': 'ansi-bright-blue',\n '95': 'ansi-bright-magenta',\n '96': 'ansi-bright-cyan',\n '97': 'ansi-bright-white',\n};\n\n/** Row height fallback when `--lc-log-viewer-line-height` can't be read (SSR, tests). */\nconst DEFAULT_LINE_HEIGHT = 22;\n\n/** Distance from the bottom (px) still counted as \"at the bottom\". */\nconst AT_BOTTOM_THRESHOLD = 30;\n\n/**\n * Streaming log / terminal viewer component.\n *\n * Supports controlled (lines input) and streaming (stream$ observable) modes,\n * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,\n * and filtering.\n *\n * The virtual window is sized from the *measured* height of the scroll area\n * (ResizeObserver), so any `height` — `400px`, `50vh`, `100%` inside a flex\n * parent — renders a full window. Row height comes from the CSS custom\n * property `--lc-log-viewer-line-height` (default 22px), read once after the\n * first render; override it on the host to change the row density.\n *\n * @example\n * ```html\n * <lc-log-viewer [stream$]=\"logs$\" autoScroll height=\"600px\" variant=\"terminal\" />\n * ```\n */\n@Component({\n selector: 'lc-log-viewer',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_TRASH, ICON_SEARCH, ICON_PLAYER_PLAY, ICON_PLAYER_PAUSE, ICON_CLIPBOARD, ICON_ARROW_DOWN]),\n ],\n templateUrl: './log-viewer.component.html',\n styleUrls: ['./log-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class LogViewerComponent {\n private static nextId = 0;\n /** Id of the search input (links its visually-hidden label). */\n protected readonly searchInputId = `lc-log-viewer-search-${++LogViewerComponent.nextId}`;\n\n private readonly ngZone = inject(NgZone);\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n private readonly scrollContainer = viewChild.required<ElementRef<HTMLElement>>('scrollContainer');\n private readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');\n private streamSub?: Subscription;\n\n /** Controlled mode: array of log lines */\n readonly lines = input<LogLine[]>([]);\n\n /** Streaming mode: observable of log lines */\n readonly stream$ = input<Observable<LogLine | LogLine[]>>();\n\n /** Maximum lines to keep in buffer */\n readonly maxLines = input<number>(10_000);\n\n /**\n * Follow new lines: scroll to the bottom whenever the visible line count\n * changes — but only while the reader is already at the bottom, so scrolling\n * up to inspect older output is never fought.\n */\n readonly autoScroll = input<boolean>(true);\n\n /** Show timestamps column */\n readonly showTimestamps = input<boolean>(true);\n\n /** Show line numbers */\n readonly showLineNumbers = input<boolean>(false);\n\n /** Parse ANSI color codes */\n readonly ansiColors = input<boolean>(true);\n\n /** Filter by log levels */\n readonly levelFilter = input<LogLevel[]>();\n\n /** Search query to highlight */\n readonly searchQuery = input<string>();\n\n /** Container height */\n readonly height = input<string>('400px');\n\n /** Visual variant */\n readonly variant = input<LogViewerVariant>('log');\n\n /**\n * Render lines as interactive rows (`role=\"button\"`, focusable, Enter/Space\n * emit `lineClick`). Set this whenever you bind `(lineClick)` so keyboard\n * users can activate a line too; a plain click emits either way.\n */\n readonly clickableLines = input<boolean>(false);\n\n /** BCP 47 locale of the line timestamps (24-hour clock). */\n readonly locale = input('en-US');\n\n /** Tooltip of the pause button while streaming. */\n readonly pauseLabel = input('Pause');\n\n /** Tooltip of the pause button while paused. */\n readonly resumeLabel = input('Resume');\n\n /** Accessible name of the pause button while streaming. */\n readonly pauseAriaLabel = input('Pause stream');\n\n /** Accessible name of the pause button while paused. */\n readonly resumeAriaLabel = input('Resume stream');\n\n /** Tooltip of the clear button. */\n readonly clearLabel = input('Clear');\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear log');\n\n /** Tooltip of the copy button. */\n readonly copyLabel = input('Copy all');\n\n /** Accessible name of the copy button. */\n readonly copyAriaLabel = input('Copy all lines');\n\n /** Tooltip of the search toggle. */\n readonly searchLabel = input('Search (/)');\n\n /** Accessible name of the search toggle. */\n readonly searchAriaLabel = input('Search log');\n\n /** Label of the search field (visually hidden). */\n readonly searchInputLabel = input('Search logs');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search logs...');\n\n /** Unit after the error count in the level filter. */\n readonly errorsLabel = input('errors');\n\n /** Unit after the warning count in the level filter. */\n readonly warningsLabel = input('warnings');\n\n /** Unit after the line count. */\n readonly linesLabel = input('lines');\n\n /** Accessible name of the log region. */\n readonly outputAriaLabel = input('Log output');\n\n /** Label of the jump-to-bottom button. */\n readonly jumpToBottomLabel = input('Jump to bottom');\n\n /** Emitted when a line is clicked (or activated with Enter/Space) */\n readonly lineClick = output<LogLine>();\n\n /**\n * Emitted with the copied text after \"Copy all\" — also when the Clipboard\n * API is unavailable (insecure context, denied permission), so consumers can\n * offer their own fallback.\n */\n readonly copyAll = output<string>();\n\n /** Emitted when scroll state changes */\n readonly scrollStateChange = output<{ atBottom: boolean }>();\n\n /** Internal buffer of all lines */\n protected buffer = signal<LogLine[]>([]);\n\n /** Whether stream is paused */\n protected paused = signal(false);\n\n /** Whether user is at bottom of scroll */\n protected atBottom = signal(true);\n\n /** Internal search input */\n protected internalSearch = signal('');\n\n /** Internal level filter */\n protected internalLevelFilter = signal<Set<LogLevel>>(new Set());\n\n /** Show search bar */\n protected showSearch = signal(false);\n\n /** Effective search query */\n protected effectiveSearch = computed(() => {\n return this.searchQuery() || this.internalSearch();\n });\n\n /** Filtered lines for display */\n protected filteredLines = computed(() => {\n let lines = this.buffer();\n const levels = this.levelFilter();\n const internalFilter = this.internalLevelFilter();\n\n // Apply level filter\n if (levels?.length) {\n lines = lines.filter((l) => l.level && levels.includes(l.level));\n } else if (internalFilter.size > 0) {\n lines = lines.filter((l) => l.level && internalFilter.has(l.level));\n }\n\n return lines;\n });\n\n // ── Virtual window ────────────────────────────────────────────────────\n\n protected scrollTop = signal(0);\n\n /**\n * Measured `clientHeight` of the scroll area (excludes toolbar and search\n * bar by construction). 0 until the first ResizeObserver callback.\n */\n private readonly viewportHeight = signal(0);\n\n /** Row height in px — `--lc-log-viewer-line-height`, read once after render. */\n protected readonly lineHeight = signal(DEFAULT_LINE_HEIGHT);\n\n protected visibleRange = computed(() => {\n const lineHeight = this.lineHeight();\n const measured = this.viewportHeight();\n // Before the first measurement fall back to the declared height (minus a\n // nominal toolbar) so the first frame already shows a plausible window.\n const viewHeight = measured > 0 ? measured : (parseInt(this.height(), 10) || 400) - 40;\n const total = this.filteredLines().length;\n const start = Math.floor(this.scrollTop() / lineHeight);\n const visible = Math.ceil(viewHeight / lineHeight) + 2;\n return {\n start: Math.max(0, start - 1),\n end: Math.min(total, start + visible + 1),\n total,\n totalHeight: total * lineHeight,\n };\n });\n\n protected visibleLines = computed(() => {\n const range = this.visibleRange();\n return this.filteredLines().slice(range.start, range.end).map((line, i) => ({\n ...line,\n _index: range.start + i,\n }));\n });\n\n protected containerClasses = computed(() => {\n return `lc-log-viewer lc-log-viewer--${this.variant()}`;\n });\n\n /** Line counts per level for toolbar */\n protected levelCounts = computed(() => {\n const buf = this.buffer();\n const counts = { debug: 0, info: 0, warn: 0, error: 0 };\n for (const line of buf) {\n if (line.level && line.level in counts) counts[line.level as keyof typeof counts]++;\n }\n return counts;\n });\n\n constructor() {\n // Controlled mode: mirror `lines` into the buffer — unconditionally, so\n // `[lines]=\"[]\"` clears the view. `maxLines` is read untracked: changing\n // it must not wipe a streamed buffer through this effect.\n effect(() => {\n const input = this.lines();\n untracked(() => this.buffer.set(this.trim(input, this.maxLines())));\n });\n\n // Streaming mode. The subscribe body runs untracked: BehaviorSubject /\n // ReplaySubject / of() emit synchronously inside this effect, and reading\n // `paused()` / `maxLines()` there would make them dependencies — toggling\n // Pause would then re-subscribe and replay duplicates.\n effect(() => {\n const stream = this.stream$();\n this.streamSub?.unsubscribe();\n this.streamSub = undefined;\n if (!stream) return;\n untracked(() => {\n this.streamSub = stream.subscribe((data) => this.append(Array.isArray(data) ? data : [data]));\n });\n });\n this.destroyRef.onDestroy(() => this.streamSub?.unsubscribe());\n\n // Follow mode: whenever the visible lines change (new lines, filter),\n // stick to the bottom — but only if the reader is already there.\n effect(() => {\n this.filteredLines();\n if (!this.autoScroll() || !untracked(this.atBottom)) return;\n afterNextRender(() => this.scrollToBottom(), { injector: this.injector });\n });\n\n afterNextRender(() => {\n const el = this.scrollContainer().nativeElement;\n\n // Row height from CSS (once) so the virtual window matches the rows.\n const declared = parseFloat(getComputedStyle(el).getPropertyValue('--lc-log-viewer-line-height'));\n if (declared > 0) this.lineHeight.set(declared);\n\n // Passive scroll tracking outside the zone.\n const onScroll = () => this.onScroll();\n this.ngZone.runOutsideAngular(() => el.addEventListener('scroll', onScroll, { passive: true }));\n this.destroyRef.onDestroy(() => el.removeEventListener('scroll', onScroll));\n\n // Measure the scroll area so `height=\"100%\"`, `50vh`, flex parents … all\n // yield a correctly sized window (not one derived from parseInt(height)).\n this.viewportHeight.set(el.clientHeight);\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => this.viewportHeight.set(el.clientHeight));\n observer.observe(el);\n this.destroyRef.onDestroy(() => observer.disconnect());\n });\n }\n\n private trim(lines: LogLine[], max: number): LogLine[] {\n return lines.length > max ? lines.slice(-max) : lines;\n }\n\n /** Append streamed lines to the buffer (no-op while paused). */\n private append(newLines: LogLine[]): void {\n if (untracked(this.paused)) return;\n const max = untracked(this.maxLines);\n this.buffer.update((buf) => this.trim([...buf, ...newLines], max));\n }\n\n protected onScroll(): void {\n const el = this.scrollContainer().nativeElement;\n this.scrollTop.set(el.scrollTop);\n const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < AT_BOTTOM_THRESHOLD;\n if (this.atBottom() !== isAtBottom) {\n this.ngZone.run(() => {\n this.atBottom.set(isAtBottom);\n this.scrollStateChange.emit({ atBottom: isAtBottom });\n });\n }\n }\n\n protected scrollToBottom(): void {\n const el = this.scrollContainer().nativeElement;\n el.scrollTop = el.scrollHeight;\n this.atBottom.set(true);\n }\n\n protected togglePause(): void {\n this.paused.update((v) => !v);\n }\n\n protected clearBuffer(): void {\n this.buffer.set([]);\n }\n\n protected async onCopyAll(): Promise<void> {\n const text = this.filteredLines()\n .map((l) => {\n const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';\n const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';\n return `${ts}${lvl}${l.text}`;\n })\n .join('\\n');\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n // Clipboard API unavailable (insecure context / permission denied):\n // still emit so the consumer can fall back to its own copy path.\n }\n this.copyAll.emit(text);\n }\n\n protected toggleSearch(): void {\n this.showSearch.update((v) => !v);\n if (this.showSearch()) {\n // The input is `@if`-rendered — focus it once it exists (`autofocus`\n // is unreliable for dynamically inserted elements).\n afterNextRender(() => this.searchInput()?.nativeElement.focus(), { injector: this.injector });\n } else {\n this.internalSearch.set('');\n }\n }\n\n protected toggleLevelFilter(level: LogLevel): void {\n this.internalLevelFilter.update((set) => {\n const newSet = new Set(set);\n if (newSet.has(level)) {\n newSet.delete(level);\n } else {\n newSet.add(level);\n }\n return newSet;\n });\n }\n\n protected onLineClick(line: LogLine): void {\n this.lineClick.emit(line);\n }\n\n /**\n * Enter/Space activate an interactive line (see `clickableLines`). Like a\n * native button, Enter fires on keydown and Space on keyup (keydown only\n * prevents the page scroll) — so a consumer that moves focus in its\n * `lineClick` handler doesn't get a stray Space keyup on the new target.\n */\n protected onLineKeydown(event: KeyboardEvent, line: LogLine): void {\n if (!this.clickableLines()) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this.lineClick.emit(line);\n } else if (event.key === ' ') {\n event.preventDefault();\n }\n }\n\n protected onLineKeyup(event: KeyboardEvent, line: LogLine): void {\n if (this.clickableLines() && event.key === ' ') {\n event.preventDefault();\n this.lineClick.emit(line);\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.showSearch()) {\n event.preventDefault();\n this.toggleSearch();\n }\n if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {\n this.scrollContainer().nativeElement.scrollTo(0, 0);\n }\n if (event.key === 'G' && !this.showSearch()) {\n this.scrollToBottom();\n }\n }\n\n protected formatTimestamp(date?: Date): string {\n if (!date) return '';\n return date.toLocaleTimeString(this.locale(), { hour12: false } as Intl.DateTimeFormatOptions);\n }\n\n /**\n * Render a line's text as HTML: ANSI SGR codes become `<span class=\"ansi-…\">`\n * wrappers, everything else is escaped, and search matches are wrapped in\n * `<mark>`. Highlighting is applied per *text* segment — before the spans\n * are assembled — so a query can never match inside a tag or an entity\n * (`e` used to hit `class=\"ansi-red\"`, `&` / `<` never matched at all).\n */\n protected renderLine(text: string): string {\n const query = this.effectiveSearch();\n const matcher = query\n ? new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi')\n : null;\n // Split the raw text on the query (capturing group → odd parts are the\n // matches), escape every part, wrap the matches.\n const renderText = (segment: string): string =>\n matcher\n ? segment\n .split(matcher)\n .map((part, i) =>\n i % 2 === 1\n ? `<mark class=\"lc-log-viewer__match\">${this.escapeHtml(part)}</mark>`\n : this.escapeHtml(part),\n )\n .join('')\n : this.escapeHtml(segment);\n\n if (!this.ansiColors()) return renderText(text);\n\n let html = '';\n let openSpans = 0;\n let cursor = 0;\n // SGR sequences: ESC [ <params> m — params are `;`-separated (may be empty = reset).\n // eslint-disable-next-line no-control-regex -- matching the ESC byte is the point here\n const re = /\\x1b\\[([\\d;]*)m/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n html += renderText(text.slice(cursor, m.index));\n cursor = m.index + m[0].length;\n for (const code of (m[1] || '0').split(';')) {\n if (code === '0' || code === '') {\n // Reset closes everything that is open.\n html += '</span>'.repeat(openSpans);\n openSpans = 0;\n } else if (ANSI_CLASSES[code]) {\n html += `<span class=\"${ANSI_CLASSES[code]}\">`;\n openSpans++;\n }\n // Unrecognised codes are stripped.\n }\n }\n html += renderText(text.slice(cursor));\n return html + '</span>'.repeat(openSpans);\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLInputElement).value;\n }\n}\n","<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-log-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/log-viewer/log-viewer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n DestroyRef,\n ElementRef,\n Injector,\n NgZone,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_ARROW_DOWN,\n ICON_CLIPBOARD,\n ICON_PLAYER_PAUSE,\n ICON_PLAYER_PLAY,\n ICON_SEARCH,\n ICON_TRASH,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\nexport type LogViewerVariant = 'terminal' | 'log';\n\nexport interface LogLine {\n text: string;\n level?: LogLevel;\n timestamp?: Date;\n source?: string;\n meta?: Record<string, unknown>;\n}\n\n/** SGR code → CSS class for the ANSI subset the viewer renders. */\nconst ANSI_CLASSES: Record<string, string> = {\n '1': 'ansi-bold',\n '3': 'ansi-italic',\n '30': 'ansi-black',\n '31': 'ansi-red',\n '32': 'ansi-green',\n '33': 'ansi-yellow',\n '34': 'ansi-blue',\n '35': 'ansi-magenta',\n '36': 'ansi-cyan',\n '37': 'ansi-white',\n '90': 'ansi-gray',\n '91': 'ansi-bright-red',\n '92': 'ansi-bright-green',\n '93': 'ansi-bright-yellow',\n '94': 'ansi-bright-blue',\n '95': 'ansi-bright-magenta',\n '96': 'ansi-bright-cyan',\n '97': 'ansi-bright-white',\n};\n\n/** Row height fallback when `--lc-log-viewer-line-height` can't be read (SSR, tests). */\nconst DEFAULT_LINE_HEIGHT = 22;\n\n/** Distance from the bottom (px) still counted as \"at the bottom\". */\nconst AT_BOTTOM_THRESHOLD = 30;\n\n/**\n * Streaming log / terminal viewer component.\n *\n * Supports controlled (lines input) and streaming (stream$ observable) modes,\n * virtualized rendering for large buffers, ANSI color parsing, auto-scroll,\n * and filtering.\n *\n * The virtual window is sized from the *measured* height of the scroll area\n * (ResizeObserver), so any `height` — `400px`, `50vh`, `100%` inside a flex\n * parent — renders a full window. Row height comes from the CSS custom\n * property `--lc-log-viewer-line-height` (default 22px), read once after the\n * first render; override it on the host to change the row density.\n *\n * @example\n * ```html\n * <lc-log-viewer [stream$]=\"logs$\" autoScroll height=\"600px\" variant=\"terminal\" />\n * ```\n */\n@Component({\n selector: 'lc-log-viewer',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_TRASH, ICON_SEARCH, ICON_PLAYER_PLAY, ICON_PLAYER_PAUSE, ICON_CLIPBOARD, ICON_ARROW_DOWN]),\n ],\n templateUrl: './log-viewer.component.html',\n styleUrls: ['./log-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class LogViewerComponent {\n private static nextId = 0;\n /** Id of the search input (links its visually-hidden label). */\n protected readonly searchInputId = `lc-log-viewer-search-${++LogViewerComponent.nextId}`;\n\n private readonly ngZone = inject(NgZone);\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n private readonly scrollContainer = viewChild.required<ElementRef<HTMLElement>>('scrollContainer');\n private readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');\n private streamSub?: Subscription;\n\n /** Controlled mode: array of log lines */\n readonly lines = input<LogLine[]>([]);\n\n /** Streaming mode: observable of log lines */\n readonly stream$ = input<Observable<LogLine | LogLine[]>>();\n\n /** Maximum lines to keep in buffer */\n readonly maxLines = input<number>(10_000);\n\n /**\n * Follow new lines: scroll to the bottom whenever the visible line count\n * changes — but only while the reader is already at the bottom, so scrolling\n * up to inspect older output is never fought.\n */\n readonly autoScroll = input<boolean>(true);\n\n /** Show timestamps column */\n readonly showTimestamps = input<boolean>(true);\n\n /** Show line numbers */\n readonly showLineNumbers = input<boolean>(false);\n\n /** Parse ANSI color codes */\n readonly ansiColors = input<boolean>(true);\n\n /** Filter by log levels */\n readonly levelFilter = input<LogLevel[]>();\n\n /** Search query to highlight */\n readonly searchQuery = input<string>();\n\n /** Container height */\n readonly height = input<string>('400px');\n\n /** Visual variant */\n readonly variant = input<LogViewerVariant>('log');\n\n /**\n * Render lines as interactive rows (`role=\"button\"`, focusable, Enter/Space\n * emit `lineClick`). Set this whenever you bind `(lineClick)` so keyboard\n * users can activate a line too; a plain click emits either way.\n */\n readonly clickableLines = input<boolean>(false);\n\n /** BCP 47 locale of the line timestamps (24-hour clock). */\n readonly locale = input('en-US');\n\n /** Tooltip of the pause button while streaming. */\n readonly pauseLabel = input('Pause');\n\n /** Tooltip of the pause button while paused. */\n readonly resumeLabel = input('Resume');\n\n /** Accessible name of the pause button while streaming. */\n readonly pauseAriaLabel = input('Pause stream');\n\n /** Accessible name of the pause button while paused. */\n readonly resumeAriaLabel = input('Resume stream');\n\n /** Tooltip of the clear button. */\n readonly clearLabel = input('Clear');\n\n /** Accessible name of the clear button. */\n readonly clearAriaLabel = input('Clear log');\n\n /** Tooltip of the copy button. */\n readonly copyLabel = input('Copy all');\n\n /** Accessible name of the copy button. */\n readonly copyAriaLabel = input('Copy all lines');\n\n /** Tooltip of the search toggle. */\n readonly searchLabel = input('Search (/)');\n\n /** Accessible name of the search toggle. */\n readonly searchAriaLabel = input('Search log');\n\n /** Label of the search field (visually hidden). */\n readonly searchInputLabel = input('Search logs');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search logs...');\n\n /** Unit after the error count in the level filter. */\n readonly errorsLabel = input('errors');\n\n /** Unit after the warning count in the level filter. */\n readonly warningsLabel = input('warnings');\n\n /** Unit after the line count. */\n readonly linesLabel = input('lines');\n\n /** Accessible name of the log region. */\n readonly outputAriaLabel = input('Log output');\n\n /** Label of the jump-to-bottom button. */\n readonly jumpToBottomLabel = input('Jump to bottom');\n\n /** Emitted when a line is clicked (or activated with Enter/Space) */\n readonly lineClick = output<LogLine>();\n\n /**\n * Emitted with the copied text after \"Copy all\" — also when the Clipboard\n * API is unavailable (insecure context, denied permission), so consumers can\n * offer their own fallback.\n */\n readonly copyAll = output<string>();\n\n /** Emitted when scroll state changes */\n readonly scrollStateChange = output<{ atBottom: boolean }>();\n\n /** Internal buffer of all lines */\n protected buffer = signal<LogLine[]>([]);\n\n /** Whether stream is paused */\n protected paused = signal(false);\n\n /** Whether user is at bottom of scroll */\n protected atBottom = signal(true);\n\n /** Internal search input */\n protected internalSearch = signal('');\n\n /** Internal level filter */\n protected internalLevelFilter = signal<Set<LogLevel>>(new Set());\n\n /** Show search bar */\n protected showSearch = signal(false);\n\n /** Effective search query */\n protected effectiveSearch = computed(() => {\n return this.searchQuery() || this.internalSearch();\n });\n\n /** Filtered lines for display */\n protected filteredLines = computed(() => {\n let lines = this.buffer();\n const levels = this.levelFilter();\n const internalFilter = this.internalLevelFilter();\n\n // Apply level filter\n if (levels?.length) {\n lines = lines.filter((l) => l.level && levels.includes(l.level));\n } else if (internalFilter.size > 0) {\n lines = lines.filter((l) => l.level && internalFilter.has(l.level));\n }\n\n return lines;\n });\n\n // ── Virtual window ────────────────────────────────────────────────────\n\n protected scrollTop = signal(0);\n\n /**\n * Measured `clientHeight` of the scroll area (excludes toolbar and search\n * bar by construction). 0 until the first ResizeObserver callback.\n */\n private readonly viewportHeight = signal(0);\n\n /** Row height in px — `--lc-log-viewer-line-height`, read once after render. */\n protected readonly lineHeight = signal(DEFAULT_LINE_HEIGHT);\n\n protected visibleRange = computed(() => {\n const lineHeight = this.lineHeight();\n const measured = this.viewportHeight();\n // Before the first measurement fall back to the declared height (minus a\n // nominal toolbar) so the first frame already shows a plausible window.\n const viewHeight = measured > 0 ? measured : (parseInt(this.height(), 10) || 400) - 40;\n const total = this.filteredLines().length;\n const start = Math.floor(this.scrollTop() / lineHeight);\n const visible = Math.ceil(viewHeight / lineHeight) + 2;\n return {\n start: Math.max(0, start - 1),\n end: Math.min(total, start + visible + 1),\n total,\n totalHeight: total * lineHeight,\n };\n });\n\n protected visibleLines = computed(() => {\n const range = this.visibleRange();\n return this.filteredLines().slice(range.start, range.end).map((line, i) => ({\n ...line,\n _index: range.start + i,\n }));\n });\n\n protected containerClasses = computed(() => {\n return `lc-log-viewer lc-log-viewer--${this.variant()}`;\n });\n\n /** Line counts per level for toolbar */\n protected levelCounts = computed(() => {\n const buf = this.buffer();\n const counts = { debug: 0, info: 0, warn: 0, error: 0 };\n for (const line of buf) {\n if (line.level && line.level in counts) counts[line.level as keyof typeof counts]++;\n }\n return counts;\n });\n\n constructor() {\n // Controlled mode: mirror `lines` into the buffer — unconditionally, so\n // `[lines]=\"[]\"` clears the view. `maxLines` is read untracked: changing\n // it must not wipe a streamed buffer through this effect.\n effect(() => {\n const input = this.lines();\n untracked(() => this.buffer.set(this.trim(input, this.maxLines())));\n });\n\n // Streaming mode. The subscribe body runs untracked: BehaviorSubject /\n // ReplaySubject / of() emit synchronously inside this effect, and reading\n // `paused()` / `maxLines()` there would make them dependencies — toggling\n // Pause would then re-subscribe and replay duplicates.\n effect(() => {\n const stream = this.stream$();\n this.streamSub?.unsubscribe();\n this.streamSub = undefined;\n if (!stream) return;\n untracked(() => {\n this.streamSub = stream.subscribe((data) => this.append(Array.isArray(data) ? data : [data]));\n });\n });\n this.destroyRef.onDestroy(() => this.streamSub?.unsubscribe());\n\n // Follow mode: whenever the visible lines change (new lines, filter),\n // stick to the bottom — but only if the reader is already there.\n effect(() => {\n this.filteredLines();\n if (!this.autoScroll() || !untracked(this.atBottom)) return;\n afterNextRender(() => this.scrollToBottom(), { injector: this.injector });\n });\n\n afterNextRender(() => {\n const el = this.scrollContainer().nativeElement;\n\n // Row height from CSS (once) so the virtual window matches the rows.\n const declared = parseFloat(getComputedStyle(el).getPropertyValue('--lc-log-viewer-line-height'));\n if (declared > 0) this.lineHeight.set(declared);\n\n // Passive scroll tracking outside the zone.\n const onScroll = () => this.onScroll();\n this.ngZone.runOutsideAngular(() => el.addEventListener('scroll', onScroll, { passive: true }));\n this.destroyRef.onDestroy(() => el.removeEventListener('scroll', onScroll));\n\n // Measure the scroll area so `height=\"100%\"`, `50vh`, flex parents … all\n // yield a correctly sized window (not one derived from parseInt(height)).\n this.viewportHeight.set(el.clientHeight);\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => this.viewportHeight.set(el.clientHeight));\n observer.observe(el);\n this.destroyRef.onDestroy(() => observer.disconnect());\n });\n }\n\n private trim(lines: LogLine[], max: number): LogLine[] {\n return lines.length > max ? lines.slice(-max) : lines;\n }\n\n /** Append streamed lines to the buffer (no-op while paused). */\n private append(newLines: LogLine[]): void {\n if (untracked(this.paused)) return;\n const max = untracked(this.maxLines);\n this.buffer.update((buf) => this.trim([...buf, ...newLines], max));\n }\n\n protected onScroll(): void {\n const el = this.scrollContainer().nativeElement;\n this.scrollTop.set(el.scrollTop);\n const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < AT_BOTTOM_THRESHOLD;\n if (this.atBottom() !== isAtBottom) {\n this.ngZone.run(() => {\n this.atBottom.set(isAtBottom);\n this.scrollStateChange.emit({ atBottom: isAtBottom });\n });\n }\n }\n\n protected scrollToBottom(): void {\n const el = this.scrollContainer().nativeElement;\n el.scrollTop = el.scrollHeight;\n this.atBottom.set(true);\n }\n\n protected togglePause(): void {\n this.paused.update((v) => !v);\n }\n\n protected clearBuffer(): void {\n this.buffer.set([]);\n }\n\n protected async onCopyAll(): Promise<void> {\n const text = this.filteredLines()\n .map((l) => {\n const ts = l.timestamp ? `[${l.timestamp.toISOString()}] ` : '';\n const lvl = l.level ? `[${l.level.toUpperCase()}] ` : '';\n return `${ts}${lvl}${l.text}`;\n })\n .join('\\n');\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n // Clipboard API unavailable (insecure context / permission denied):\n // still emit so the consumer can fall back to its own copy path.\n }\n this.copyAll.emit(text);\n }\n\n protected toggleSearch(): void {\n this.showSearch.update((v) => !v);\n if (this.showSearch()) {\n // The input is `@if`-rendered — focus it once it exists (`autofocus`\n // is unreliable for dynamically inserted elements).\n afterNextRender(() => this.searchInput()?.nativeElement.focus(), { injector: this.injector });\n } else {\n this.internalSearch.set('');\n }\n }\n\n protected toggleLevelFilter(level: LogLevel): void {\n this.internalLevelFilter.update((set) => {\n const newSet = new Set(set);\n if (newSet.has(level)) {\n newSet.delete(level);\n } else {\n newSet.add(level);\n }\n return newSet;\n });\n }\n\n protected onLineClick(line: LogLine): void {\n this.lineClick.emit(line);\n }\n\n /**\n * Enter/Space activate an interactive line (see `clickableLines`). Like a\n * native button, Enter fires on keydown and Space on keyup (keydown only\n * prevents the page scroll) — so a consumer that moves focus in its\n * `lineClick` handler doesn't get a stray Space keyup on the new target.\n */\n protected onLineKeydown(event: KeyboardEvent, line: LogLine): void {\n if (!this.clickableLines()) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this.lineClick.emit(line);\n } else if (event.key === ' ') {\n event.preventDefault();\n }\n }\n\n protected onLineKeyup(event: KeyboardEvent, line: LogLine): void {\n if (this.clickableLines() && event.key === ' ') {\n event.preventDefault();\n this.lineClick.emit(line);\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.showSearch()) {\n event.preventDefault();\n this.toggleSearch();\n }\n if (event.key === 'g' && !event.shiftKey && !this.showSearch()) {\n this.scrollContainer().nativeElement.scrollTo(0, 0);\n }\n if (event.key === 'G' && !this.showSearch()) {\n this.scrollToBottom();\n }\n }\n\n protected formatTimestamp(date?: Date): string {\n if (!date) return '';\n return date.toLocaleTimeString(this.locale(), { hour12: false } as Intl.DateTimeFormatOptions);\n }\n\n /**\n * Render a line's text as HTML: ANSI SGR codes become `<span class=\"ansi-…\">`\n * wrappers, everything else is escaped, and search matches are wrapped in\n * `<mark>`. Highlighting is applied per *text* segment — before the spans\n * are assembled — so a query can never match inside a tag or an entity\n * (`e` used to hit `class=\"ansi-red\"`, `&` / `<` never matched at all).\n */\n protected renderLine(text: string): string {\n const query = this.effectiveSearch();\n const matcher = query\n ? new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi')\n : null;\n // Split the raw text on the query (capturing group → odd parts are the\n // matches), escape every part, wrap the matches.\n const renderText = (segment: string): string =>\n matcher\n ? segment\n .split(matcher)\n .map((part, i) =>\n i % 2 === 1\n ? `<mark class=\"lc-log-viewer__match\">${this.escapeHtml(part)}</mark>`\n : this.escapeHtml(part),\n )\n .join('')\n : this.escapeHtml(segment);\n\n if (!this.ansiColors()) return renderText(text);\n\n let html = '';\n let openSpans = 0;\n let cursor = 0;\n // SGR sequences: ESC [ <params> m — params are `;`-separated (may be empty = reset).\n // eslint-disable-next-line no-control-regex -- matching the ESC byte is the point here\n const re = /\\x1b\\[([\\d;]*)m/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n html += renderText(text.slice(cursor, m.index));\n cursor = m.index + m[0].length;\n for (const code of (m[1] || '0').split(';')) {\n if (code === '0' || code === '') {\n // Reset closes everything that is open.\n html += '</span>'.repeat(openSpans);\n openSpans = 0;\n } else if (ANSI_CLASSES[code]) {\n html += `<span class=\"${ANSI_CLASSES[code]}\">`;\n openSpans++;\n }\n // Unrecognised codes are stripped.\n }\n }\n html += renderText(text.slice(cursor));\n return html + '</span>'.repeat(openSpans);\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLInputElement).value;\n }\n}\n","<div\n [class]=\"containerClasses()\"\n [style.height]=\"height()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Toolbar -->\n <div class=\"lc-log-viewer__toolbar\">\n <div class=\"lc-log-viewer__toolbar-left\">\n @if (stream$()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"paused()\"\n (click)=\"togglePause()\"\n [title]=\"paused() ? resumeLabel() : pauseLabel()\"\n [attr.aria-label]=\"paused() ? resumeAriaLabel() : pauseAriaLabel()\"\n [attr.aria-pressed]=\"paused()\"\n >\n <lc-icon [name]=\"paused() ? 'player-play' : 'player-pause'\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"clearBuffer()\"\n [attr.title]=\"clearLabel()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n >\n <lc-icon name=\"trash\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n (click)=\"onCopyAll()\"\n [attr.title]=\"copyLabel()\"\n [attr.aria-label]=\"copyAriaLabel()\"\n >\n <lc-icon name=\"clipboard-document\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"lc-log-viewer__tool-btn\"\n [class.lc-log-viewer__tool-btn--active]=\"showSearch()\"\n (click)=\"toggleSearch()\"\n [attr.title]=\"searchLabel()\"\n [attr.aria-label]=\"searchAriaLabel()\"\n [attr.aria-expanded]=\"showSearch()\"\n >\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n\n <div class=\"lc-log-viewer__toolbar-right\">\n <!-- Level filter pills -->\n @if (levelCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--error\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('error')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('error')\"\n (click)=\"toggleLevelFilter('error')\"\n >\n {{ levelCounts().error }} {{ errorsLabel() }}\n </button>\n }\n @if (levelCounts().warn > 0) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__level-pill lc-log-viewer__level-pill--warn\"\n [class.lc-log-viewer__level-pill--active]=\"internalLevelFilter().has('warn')\"\n [attr.aria-pressed]=\"internalLevelFilter().has('warn')\"\n (click)=\"toggleLevelFilter('warn')\"\n >\n {{ levelCounts().warn }} {{ warningsLabel() }}\n </button>\n }\n <span class=\"lc-log-viewer__line-count\">{{ filteredLines().length }} {{ linesLabel() }}</span>\n </div>\n </div>\n\n <!-- Search bar -->\n @if (showSearch()) {\n <div class=\"lc-log-viewer__search\">\n <lc-icon name=\"magnifying-glass\" size=\"xs\" [decorative]=\"true\" />\n <label class=\"lc-log-viewer__sr-only\" [for]=\"searchInputId\">{{ searchInputLabel() }}</label>\n <input\n #searchInput\n type=\"text\"\n [id]=\"searchInputId\"\n class=\"lc-log-viewer__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"internalSearch()\"\n (input)=\"internalSearch.set(getInputValue($event))\"\n />\n </div>\n }\n\n <!--\n Log content (virtualized). `role=\"log\"` names the region; live\n announcements are switched off because rows enter/leave the DOM on every\n scroll of the virtual window — announcing them would be pure noise.\n -->\n <div\n #scrollContainer\n class=\"lc-log-viewer__content\"\n role=\"log\"\n aria-live=\"off\"\n [attr.aria-label]=\"outputAriaLabel()\"\n >\n <div\n class=\"lc-log-viewer__spacer\"\n [style.height.px]=\"visibleRange().totalHeight\"\n >\n <div\n class=\"lc-log-viewer__window\"\n [style.transform]=\"'translateY(' + (visibleRange().start * lineHeight()) + 'px)'\"\n >\n @for (line of visibleLines(); track line._index) {\n <div\n class=\"lc-log-viewer__line\"\n [class.lc-log-viewer__line--clickable]=\"clickableLines()\"\n [class.lc-log-viewer__line--debug]=\"line.level === 'debug'\"\n [class.lc-log-viewer__line--info]=\"line.level === 'info'\"\n [class.lc-log-viewer__line--warn]=\"line.level === 'warn'\"\n [class.lc-log-viewer__line--error]=\"line.level === 'error'\"\n [attr.role]=\"clickableLines() ? 'button' : null\"\n [attr.tabindex]=\"clickableLines() ? 0 : null\"\n (click)=\"onLineClick(line)\"\n (keydown)=\"onLineKeydown($event, line)\"\n (keyup)=\"onLineKeyup($event, line)\"\n >\n @if (showLineNumbers()) {\n <span class=\"lc-log-viewer__line-num\">{{ line._index + 1 }}</span>\n }\n @if (showTimestamps() && line.timestamp) {\n <span class=\"lc-log-viewer__timestamp\">{{ formatTimestamp(line.timestamp) }}</span>\n }\n @if (line.level) {\n <span class=\"lc-log-viewer__level\">{{ line.level.toUpperCase().padEnd(5) }}</span>\n }\n @if (line.source) {\n <span class=\"lc-log-viewer__source\">[{{ line.source }}]</span>\n }\n <span\n class=\"lc-log-viewer__text\"\n [innerHTML]=\"renderLine(line.text)\"\n ></span>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Jump to bottom button -->\n @if (!atBottom()) {\n <button\n type=\"button\"\n class=\"lc-log-viewer__jump-bottom\"\n (click)=\"scrollToBottom()\"\n >\n <lc-icon name=\"arrow-down\" size=\"xs\" [decorative]=\"true\" />\n {{ jumpToBottomLabel() }}\n </button>\n }\n</div>\n"],"mappings":";;;;AAyCA,MAAM,eAAuC;CAC3C,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;AACP;AAGD,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAgC5B,IAAa,qBAAb,MAAa,mBAAkB;CACrB,OAAO,SAAS;CAEL,gBAAgB,wBAAwB,EAAE,mBAAmB;CAE/D,SAAS,OAAO,MAAM;CACtB,WAAW,OAAO,QAAQ;CAC1B,aAAa,OAAO,UAAU;CAC9B,kBAAkB,UAAU,SAAkC,mBAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAChF,cAAc,UAAwC,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC7E;CAGC,QAAQ,MAAiB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAG5B,UAAU,MAAwC,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAGlD,WAAW,MAAc,KAAO,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAOhC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,iBAAiB,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,kBAAkB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,aAAa,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAmB,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,cAAc,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,SAAS,MAAc,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,UAAU,MAAwB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAOxC,iBAAiB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,SAAS,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,iBAAiB,MAAM,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,kBAAkB,MAAM,iBAAgB,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,iBAAiB,MAAM,aAAY,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGnC,YAAY,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,cAAc,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,mBAAmB,MAAM,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGvC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,cAAc,MAAM,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,gBAAgB,MAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,aAAa,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,kBAAkB,MAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGrC,oBAAoB,MAAM,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,YAAY,OAAM;CAOlB,UAAU,OAAM;CAGhB,oBAAoB,OAAM;CAGzB,SAAS,OAAkB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,SAAS,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGtB,WAAW,OAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGvB,iBAAiB,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,sBAAsB,uBAAsB,IAAI,IAAG,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,sBAAA,CAAA,+BAAA,CAAA,CAAA;CAGtD,aAAa,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG1B,kBAAkB,eAAc;EACxC,OAAO,KAAK,YAAW,KAAM,KAAK,eAAc;CAClD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,gBAAgB,eAAc;EACtC,IAAI,QAAQ,KAAK,OAAM;EACvB,MAAM,SAAS,KAAK,YAAW;EAC/B,MAAM,iBAAiB,KAAK,oBAAmB;EAG/C,IAAI,QAAQ,QACV,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,OAAO,SAAS,EAAE,KAAK,CAAC;OAC1D,IAAI,eAAe,OAAO,GAC/B,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS,eAAe,IAAI,EAAE,KAAK,CAAC;EAGpE,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAIQ,YAAY,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAMd,iBAAiB,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,aAAa,OAAO,qBAAoB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEjD,eAAe,eAAc;EACrC,MAAM,aAAa,KAAK,WAAU;EAClC,MAAM,WAAW,KAAK,eAAc;EAGpC,MAAM,aAAa,WAAW,IAAI,YAAY,SAAS,KAAK,OAAM,GAAI,EAAE,KAAK,OAAO;EACpF,MAAM,QAAQ,KAAK,cAAa,CAAE,CAAC;EACnC,MAAM,QAAQ,KAAK,MAAM,KAAK,UAAS,IAAK,UAAU;EACtD,MAAM,UAAU,KAAK,KAAK,aAAa,UAAU,IAAI;EACrD,OAAO;GACL,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC;GAC5B,KAAK,KAAK,IAAI,OAAO,QAAQ,UAAU,CAAC;GACxC;GACA,aAAa,QAAQ;EACtB;CACH,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,eAAe,eAAc;EACrC,MAAM,QAAQ,KAAK,aAAY;EAC/B,OAAO,KAAK,cAAa,CAAE,CAAC,MAAM,MAAM,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO;GAC1E,GAAG;GACH,QAAQ,MAAM,QAAQ;EACvB,EAAC;CACJ,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,mBAAmB,eAAc;EACzC,OAAO,gCAAgC,KAAK,QAAO;CACrD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGQ,cAAc,eAAc;EACpC,MAAM,MAAM,KAAK,OAAM;EACvB,MAAM,SAAS;GAAE,OAAO;GAAG,MAAM;GAAG,MAAM;GAAG,OAAO;EAAC;EACrD,KAAK,MAAM,QAAQ,KACjB,IAAI,KAAK,SAAS,KAAK,SAAS,QAAQ,OAAO,KAAK,MAA6B;EAEnF,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EAIE,aAAY;GACV,MAAM,QAAQ,KAAK,MAAK;GACxB,gBAAgB,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,SAAQ,CAAE,CAAC,CAAC;EACpE,CAAC;EAMD,aAAY;GACV,MAAM,SAAS,KAAK,QAAO;GAC3B,KAAK,WAAW,YAAW;GAC3B,KAAK,YAAY;GACjB,IAAI,CAAC,QAAQ;GACb,gBAAe;IACb,KAAK,YAAY,OAAO,WAAW,SAAS,KAAK,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;GAC9F,CAAC;EACH,CAAC;EACD,KAAK,WAAW,gBAAgB,KAAK,WAAW,YAAW,CAAE;EAI7D,aAAY;GACV,KAAK,cAAa;GAClB,IAAI,CAAC,KAAK,WAAU,KAAM,CAAC,UAAU,KAAK,QAAQ,GAAG;GACrD,sBAAsB,KAAK,eAAc,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;EAC1E,CAAC;EAED,sBAAqB;GACnB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;GAGlC,MAAM,WAAW,WAAW,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,6BAA6B,CAAC;GAChG,IAAI,WAAW,GAAG,KAAK,WAAW,IAAI,QAAQ;GAG9C,MAAM,iBAAiB,KAAK,SAAQ;GACpC,KAAK,OAAO,wBAAwB,GAAG,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAI,CAAE,CAAC;GAC9F,KAAK,WAAW,gBAAgB,GAAG,oBAAoB,UAAU,QAAQ,CAAC;GAI1E,KAAK,eAAe,IAAI,GAAG,YAAY;GACvC,IAAI,OAAO,mBAAmB,aAAa;GAC3C,MAAM,WAAW,IAAI,qBAAqB,KAAK,eAAe,IAAI,GAAG,YAAY,CAAC;GAClF,SAAS,QAAQ,EAAE;GACnB,KAAK,WAAW,gBAAgB,SAAS,WAAU,CAAE;EACvD,CAAC;CACH;CAEQ,KAAK,OAAkB,KAAW;EACxC,OAAO,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI;CAClD;CAGQ,OAAO,UAAmB;EAChC,IAAI,UAAU,KAAK,MAAM,GAAG;EAC5B,MAAM,MAAM,UAAU,KAAK,QAAQ;EACnC,KAAK,OAAO,QAAQ,QAAQ,KAAK,KAAK,CAAC,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,CAAC;CACnE;CAEU,WAAQ;EAChB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,KAAK,UAAU,IAAI,GAAG,SAAS;EAC/B,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EACtE,IAAI,KAAK,SAAQ,MAAO,YACtB,KAAK,OAAO,UAAS;GACnB,KAAK,SAAS,IAAI,UAAU;GAC5B,KAAK,kBAAkB,KAAK,EAAE,UAAU,WAAU,CAAE;EACtD,CAAC;CAEL;CAEU,iBAAc;EACtB,MAAM,KAAK,KAAK,gBAAe,CAAE,CAAC;EAClC,GAAG,YAAY,GAAG;EAClB,KAAK,SAAS,IAAI,IAAI;CACxB;CAEU,cAAW;EACnB,KAAK,OAAO,QAAQ,MAAM,CAAC,CAAC;CAC9B;CAEU,cAAW;EACnB,KAAK,OAAO,IAAI,CAAA,CAAE;CACpB;CAEU,MAAM,YAAS;EACvB,MAAM,OAAO,KAAK,cAAa,CAAE,CAC9B,KAAK,MAAK;GAGT,OAAO,GAFI,EAAE,YAAY,IAAI,EAAE,UAAU,YAAW,EAAE,MAAO,KACjD,EAAE,QAAQ,IAAI,EAAE,MAAM,YAAW,EAAE,MAAO,KACjC,EAAE;EACzB,CAAC,CAAC,CACD,KAAK,IAAI;EACZ,IAAI;GACF,MAAM,UAAU,UAAU,UAAU,IAAI;EAC1C,QAAQ,CAGR;EACA,KAAK,QAAQ,KAAK,IAAI;CACxB;CAEU,eAAY;EACpB,KAAK,WAAW,QAAQ,MAAM,CAAC,CAAC;EAChC,IAAI,KAAK,WAAU,GAGjB,sBAAsB,KAAK,YAAW,CAAE,EAAE,cAAc,MAAK,GAAI,EAAE,UAAU,KAAK,SAAQ,CAAE;OAE5F,KAAK,eAAe,IAAI,EAAE;CAE9B;CAEU,kBAAkB,OAAe;EACzC,KAAK,oBAAoB,QAAQ,QAAO;GACtC,MAAM,SAAS,IAAI,IAAI,GAAG;GAC1B,IAAI,OAAO,IAAI,KAAK,GAClB,OAAO,OAAO,KAAK;QAEnB,OAAO,IAAI,KAAK;GAElB,OAAO;EACT,CAAC;CACH;CAEU,YAAY,MAAa;EACjC,KAAK,UAAU,KAAK,IAAI;CAC1B;CAQU,cAAc,OAAsB,MAAa;EACzD,IAAI,CAAC,KAAK,eAAc,GAAI;EAC5B,IAAI,MAAM,QAAQ,SAAS;GACzB,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B,OAAO,IAAI,MAAM,QAAQ,KACvB,MAAM,eAAc;CAExB;CAEU,YAAY,OAAsB,MAAa;EACvD,IAAI,KAAK,eAAc,KAAM,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CAEU,UAAU,OAAoB;EACtC,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GAAI;GAC3C,MAAM,eAAc;GACpB,KAAK,aAAY;EACnB;EACA,IAAI,MAAM,QAAQ,OAAO,CAAC,MAAM,YAAY,CAAC,KAAK,WAAU,GAC1D,KAAK,gBAAe,CAAE,CAAC,cAAc,SAAS,GAAG,CAAC;EAEpD,IAAI,MAAM,QAAQ,OAAO,CAAC,KAAK,WAAU,GACvC,KAAK,eAAc;CAEvB;CAEU,gBAAgB,MAAW;EACnC,IAAI,CAAC,MAAM,OAAO;EAClB,OAAO,KAAK,mBAAmB,KAAK,OAAM,GAAI,EAAE,QAAQ,MAAK,CAAgC;CAC/F;CASU,WAAW,MAAY;EAC/B,MAAM,QAAQ,KAAK,gBAAe;EAClC,MAAM,UAAU,QACZ,IAAI,OAAO,IAAI,MAAM,QAAQ,uBAAuB,MAAM,EAAC,IAAK,IAAI,IACpE;EAGJ,MAAM,cAAc,YAClB,UACI,QACG,MAAM,OAAO,CAAC,CACd,KAAK,MAAM,MACV,IAAI,MAAM,IACN,sCAAsC,KAAK,WAAW,IAAI,EAAC,WAC3D,KAAK,WAAW,IAAI,CAAC,CAC1B,CACA,KAAK,EAAE,IACV,KAAK,WAAW,OAAO;EAE7B,IAAI,CAAC,KAAK,WAAU,GAAI,OAAO,WAAW,IAAI;EAE9C,IAAI,OAAO;EACX,IAAI,YAAY;EAChB,IAAI,SAAS;EAGb,MAAM,KAAK;EACX,IAAI;EACJ,QAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;GACnC,QAAQ,WAAW,KAAK,MAAM,QAAQ,EAAE,KAAK,CAAC;GAC9C,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC;GACxB,KAAK,MAAM,SAAS,EAAE,MAAM,IAAA,CAAK,MAAM,GAAG,GACxC,IAAI,SAAS,OAAO,SAAS,IAAI;IAE/B,QAAQ,UAAU,OAAO,SAAS;IAClC,YAAY;GACd,OAAO,IAAI,aAAa,OAAO;IAC7B,QAAQ,gBAAgB,aAAa,MAAK;IAC1C;GACF;EAGJ;EACA,QAAQ,WAAW,KAAK,MAAM,MAAM,CAAC;EACrC,OAAO,OAAO,UAAU,OAAO,SAAS;CAC1C;CAEQ,WAAW,MAAY;EAC7B,OAAO,KACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM;CACzB;CAEU,cAAc,OAAY;EAClC,OAAQ,MAAM,OAA4B;CAC5C;CArcW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAlB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAkB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,kBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,WAAA;GAAA,SAAA;GAAA,mBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,iBAAA;GAAA,aAAA;GAAA,UAAA;EAAA,GAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,aAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UClG/B;EAsKA,QAAA,CAAA,g2TAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD7EY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eACR,CACb,iBAAiB;GAAC;GAAY;GAAa;GAAkB;GAAmB;GAAgB;EAAe,CAAC,CACjH;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAMU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA;EAZ9B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAe,YACb;GAAI,SACP,CAAC,aAAa;GAAC,eACT,CACb,iBAAiB;IAAC;IAAY;IAAa;IAAkB;IAAmB;IAAgB;GAAe,CAAC,CACjH;GAAA,iBAGgB,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,UAAA;GAAA,QAAA,CAAA,g2TAAA;EAAA,CAAA;CAU0C,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAiB,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACzB,eAAa,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,iBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,WAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,SAAA;EAAA,CAAA;EAAA,mBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,mBAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
|