@lifecorner/angular-ui-kit 3.21.0 → 3.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-code-block.mjs +3 -3
- package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +3 -3
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +11 -11
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.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-file-upload.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-icon.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-icon.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-icons.mjs +80 -2
- package/fesm2022/lifecorner-angular-ui-kit-icons.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs +34 -2
- package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +9 -9
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +19 -5
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-markdown.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-markdown.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-pipeline.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-pipeline.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rating.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-rating.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs +3 -3
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-status-dot.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-status-dot.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs +3 -1
- package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/theme-contrast.spec.ts +239 -0
- package/types/lifecorner-angular-ui-kit-icons.d.ts +14 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-markdown.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.ts","../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n signal,\n effect,\n untracked,\n inject,\n ElementRef,\n OnDestroy,\n SecurityContext,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { HttpClient } from '@angular/common/http';\nimport { Subscription } from 'rxjs';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport interface MarkdownHeading {\n level: number;\n text: string;\n id: string;\n}\n\nexport interface MarkdownLinkClick {\n href: string;\n event: MouseEvent;\n}\n\nexport interface MarkdownRendered {\n headings: MarkdownHeading[];\n}\n\nexport interface MarkdownChangesHighlighted {\n /** Number of changed/added blocks highlighted in the current render. */\n changedBlocks: number;\n}\n\nexport interface RenderPart {\n type: 'html' | 'code' | 'mermaid';\n index: number;\n safeHtml?: SafeHtml;\n code?: string;\n lang?: CodeBlockLanguage;\n}\n\n/**\n * Stand-in for a fenced code block while the rest of the markdown is processed.\n * Bracketed in NUL characters, which the source is stripped of first — so no\n * text a user writes can ever spell a placeholder and be mistaken for a block.\n * (The earlier `<!--CODE_BLOCK_N-->` form was: an escaped literal comment in\n * the text matched it and dereferenced a block that wasn't there.)\n */\nconst BLOCK_MARK = '\\u0000';\nconst blockPlaceholder = (idx: number): string => `${BLOCK_MARK}LCBLOCK${idx}${BLOCK_MARK}`;\n// eslint-disable-next-line no-control-regex -- the NUL bracket is the point: unforgeable\nconst BLOCK_PLACEHOLDER = /\\u0000LCBLOCK(\\d+)\\u0000/g;\n\n/** Instances numbered so their mermaid render ids never collide on one page. */\nlet markdownInstances = 0;\n\n/**\n * Markdown renderer component.\n *\n * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with\n * optional syntax highlighting via `<lc-code-block>`.\n *\n * Supports headings (+ anchors), paragraphs, ordered/unordered lists, task lists\n * (`- [ ]` / `- [x]` → accessible checkbox), blockquotes, horizontal rules,\n * images, GFM tables (column alignment + horizontal scroll), bold/italic/\n * strikethrough, inline code, fenced code (incl. `mermaid`), explicit links and\n * autolinks (bare URLs / `www.` / emails, never inside code).\n *\n * Optionally highlights *changed* blocks in place: pass the pre-edit markdown as\n * `previousContent` and set `highlightChanges` — added/edited blocks (diffed at\n * block / list-item level) gain a left accent bar + subtle tint, can auto-fade\n * (`changeHighlightFadeMs`) and scroll into view (`scrollToFirstChange`).\n *\n * @example\n * ```html\n * <lc-markdown [content]=\"'# Hello World'\" />\n * <lc-markdown [src]=\"'/docs/readme.md'\" />\n * <lc-markdown\n * [content]=\"current\" [previousContent]=\"prev\"\n * [highlightChanges]=\"true\" [changeHighlightFadeMs]=\"3000\" />\n * ```\n */\n@Component({\n selector: 'lc-markdown',\n standalone: true,\n imports: [CodeBlockComponent],\n templateUrl: './markdown.component.html',\n styleUrls: ['./markdown.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class MarkdownComponent implements OnDestroy {\n private readonly sanitizer = inject(DomSanitizer);\n private readonly instanceId = ++markdownInstances;\n private readonly http = inject(HttpClient);\n private readonly host = inject(ElementRef<HTMLElement>);\n private httpSub?: Subscription;\n private fadeTimer?: ReturnType<typeof setTimeout>;\n private scrollTimer?: ReturnType<typeof setTimeout>;\n private mermaidApiPromise?: Promise<any>;\n private mermaidInitialized = false;\n private mermaidRenderRun = 0;\n\n /** URL or path to load markdown from */\n readonly src = input<string>();\n\n /** Raw markdown string */\n readonly content = input<string>();\n\n /** Display variant */\n readonly variant = input<'default' | 'compact' | 'chat'>('default');\n\n /** Target for links */\n readonly linkTarget = input<'_self' | '_blank'>('_self');\n\n /** Whether to sanitize HTML output */\n readonly sanitize = input<boolean>(true);\n\n /** Whether to use code-block for fenced code */\n readonly syntaxHighlight = input<boolean>(true);\n\n /** Whether to show anchor links on headings */\n readonly showHeadingAnchors = input<boolean>(false);\n\n /** Base URL for resolving relative links/images */\n readonly baseUrl = input<string>();\n\n // -- Change highlighting --\n /** Enable change highlighting (requires `previousContent` to compute a diff). */\n readonly highlightChanges = input<boolean>(false);\n\n /**\n * The prior Markdown. When it differs from `content`, the changed/added blocks\n * in `content` are highlighted. The caller passes the pre-edit markdown.\n */\n readonly previousContent = input<string>();\n\n /** Auto-fade the highlight after N ms. 0 / undefined ⇒ persist until content changes. */\n readonly changeHighlightFadeMs = input<number>();\n\n /** Scroll the first changed block into view when highlights appear. */\n readonly scrollToFirstChange = input<boolean>(false);\n\n /** Screen-reader marker put in front of each highlighted block (`highlightChanges`). */\n readonly changedMarkerLabel = input('(geändert)');\n\n /** Announced after the count when one block changed: `1 Abschnitt geändert`. */\n readonly changedSummaryOneLabel = input('Abschnitt geändert');\n\n /** Announced after the count when several blocks changed: `3 Abschnitte geändert`. */\n readonly changedSummaryManyLabel = input('Abschnitte geändert');\n\n /** Shown while a Mermaid diagram renders. */\n readonly mermaidLoadingLabel = input('Rendering Mermaid diagram...');\n\n /** Shown above the source when a Mermaid diagram fails. */\n readonly mermaidErrorLabel = input('Mermaid diagram could not be rendered.');\n\n /** Shown above the source when the optional `mermaid` package cannot be loaded. */\n readonly mermaidUnavailableLabel = input('Mermaid runtime is not available.');\n\n /** Emitted when a link is clicked */\n readonly linkClick = output<MarkdownLinkClick>();\n\n /** Emitted after rendering with heading TOC */\n readonly rendered = output<MarkdownRendered>();\n\n /** Emitted after a render that produced change highlights. */\n readonly changesHighlighted = output<MarkdownChangesHighlighted>();\n\n /** Internal resolved markdown source */\n protected resolvedMarkdown = signal<string>('');\n\n /** Parsed result (computed once from resolvedMarkdown) */\n private parsed = computed(() => {\n const md = this.resolvedMarkdown();\n if (!md) {\n return {\n html: '',\n blocks: [] as { kind: 'code' | 'mermaid'; lang: string; code: string }[],\n headings: [] as MarkdownHeading[],\n };\n }\n return this.parseMarkdown(md);\n });\n\n /**\n * Rendered HTML after applying change highlights. When highlighting is off (or\n * there is no differing `previousContent`) this returns the parsed HTML\n * unchanged, so the non-highlight path is byte-for-byte identical to before.\n */\n private readonly highlightResult = computed<{ html: string; count: number }>(() => {\n const { html } = this.parsed();\n const prev = this.previousContent();\n if (\n !html ||\n !this.highlightChanges() ||\n prev == null ||\n prev === this.resolvedMarkdown()\n ) {\n return { html, count: 0 };\n }\n return this.applyChangeHighlights(html, prev);\n });\n\n /** Number of changed/added blocks highlighted in the current render. */\n protected readonly changedCount = computed(() => this.highlightResult().count);\n\n /** Visually-hidden polite summary announced when highlights appear. */\n protected readonly changeSummary = computed(() => {\n const n = this.changedCount();\n if (n <= 0) return '';\n return `${n} ${n === 1 ? this.changedSummaryOneLabel() : this.changedSummaryManyLabel()}`;\n });\n\n /** Whether the current highlights have faded out (after `changeHighlightFadeMs`). */\n protected readonly highlightsFaded = signal(false);\n\n /** Computed render parts (HTML chunks + code blocks interleaved) */\n protected renderParts = computed<RenderPart[]>(() => {\n const html = this.highlightResult().html;\n const { blocks } = this.parsed();\n if (!html) return [];\n\n if (this.syntaxHighlight() && blocks.length > 0) {\n return this.splitIntoParts(html, blocks);\n }\n\n // No code blocks — single HTML part\n return [{\n type: 'html',\n index: 0,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(html, { next: 0 })),\n }];\n });\n\n /**\n * The HTML as it may go into the DOM. With `sanitize` on (the default) that is\n * Angular's HTML sanitizer's output — which drops every `id`, since `id` isn't\n * on its attribute allow-list. The heading ids are put back afterwards: they\n * are what anchors and the `rendered` TOC point at, and each is a `[\\w-]`\n * slug this parser produced itself, so re-adding them reopens nothing. The\n * cursor walks the parsed headings in document order across the parts.\n */\n private sanitizeHtml(html: string, cursor: { next: number }): string {\n const headings = this.headings();\n if (!this.sanitize()) {\n cursor.next += (html.match(/<h[1-6]\\b/g) ?? []).length;\n return html;\n }\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html) || '';\n return sanitized.replace(/<h([1-6])(\\s[^>]*)?>/g, (tag, level: string, attrs = '') => {\n const heading = headings[cursor.next++];\n if (!heading || /\\sid=/.test(attrs)) return tag;\n return `<h${level} id=\"${heading.id}\"${attrs}>`;\n });\n }\n\n protected containerClasses = computed(() => {\n let classes = `lc-markdown lc-markdown--${this.variant()}`;\n if (this.highlightsFaded()) classes += ' lc-markdown--faded';\n return classes;\n });\n\n protected mermaidSvgs = signal<Record<number, SafeHtml>>({});\n protected mermaidErrors = signal<Record<number, string>>({});\n\n private headings = computed(() => this.parsed().headings);\n\n constructor() {\n // Load content from src when src changes\n effect(() => {\n const src = this.src();\n if (src) {\n this.loadFromUrl(src);\n }\n });\n\n // Use raw content when provided\n effect(() => {\n const content = this.content();\n if (content !== undefined) {\n this.resolvedMarkdown.set(content);\n }\n });\n\n // Emit rendered event whenever a render produced a (possibly empty) TOC\n effect(() => {\n const h = this.headings();\n untracked(() => this.rendered.emit({ headings: h }));\n });\n\n // Render Mermaid code fences as inline SVG diagrams.\n effect(() => {\n void this.renderMermaidParts(this.renderParts());\n });\n\n // React to a render that produced change highlights: emit, schedule fade,\n // and optionally scroll the first changed block into view.\n effect(() => {\n const count = this.highlightResult().count;\n untracked(() => this.onHighlightResult(count));\n });\n }\n\n ngOnDestroy(): void {\n this.httpSub?.unsubscribe();\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n }\n\n protected onLinkClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (anchor) {\n this.linkClick.emit({ href: anchor.href, event });\n }\n }\n\n // -- Change highlighting -----------------------------------------------------\n\n /** Side-effects for a render: emit, (re)arm the fade timer, optional scroll. */\n private onHighlightResult(count: number): void {\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n // A fresh render starts un-faded (re-entrant: never reuse stale fade state).\n this.highlightsFaded.set(false);\n if (count <= 0) return;\n\n this.changesHighlighted.emit({ changedBlocks: count });\n\n const fadeMs = this.changeHighlightFadeMs();\n if (fadeMs && fadeMs > 0) {\n this.fadeTimer = setTimeout(() => this.highlightsFaded.set(true), fadeMs);\n }\n if (this.scrollToFirstChange()) {\n // Defer until the highlighted HTML has been written to the DOM.\n this.scrollTimer = setTimeout(() => this.scrollToFirstChanged(), 0);\n }\n }\n\n private scrollToFirstChanged(): void {\n if (typeof document === 'undefined') return;\n const el = this.host.nativeElement.querySelector('.lc-markdown__block--changed');\n el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n /**\n * Wraps the changed/added top-level blocks of `html` with\n * `.lc-markdown__block--changed`. A block is \"changed\" when its normalized\n * text is not present among the blocks of `prevMarkdown`. Lists are diffed\n * per `<li>` and tables per `<tr>` so a single edited item highlights alone.\n */\n private applyChangeHighlights(\n html: string,\n prevMarkdown: string\n ): { html: string; count: number } {\n if (typeof document === 'undefined') return { html, count: 0 };\n\n const prevKeys = this.collectBlockKeys(this.parseMarkdown(prevMarkdown).html);\n const root = this.htmlToElement(html);\n if (!root) return { html, count: 0 };\n\n let count = 0;\n const markIfChanged = (el: Element): void => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (!key || prevKeys.has(key)) return;\n el.classList.add('lc-markdown__block--changed');\n const sr = document.createElement('span');\n sr.className = 'lc-markdown__sr-only';\n sr.textContent = `${this.changedMarkerLabel()} `;\n el.insertBefore(sr, el.firstChild);\n count++;\n };\n\n this.eachLogicalBlock(root, markIfChanged);\n return { html: root.innerHTML, count };\n }\n\n /** Normalized text set of the logical blocks within an HTML fragment. */\n private collectBlockKeys(html: string): Set<string> {\n const keys = new Set<string>();\n const root = this.htmlToElement(html);\n if (!root) return keys;\n this.eachLogicalBlock(root, (el) => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (key) keys.add(key);\n });\n return keys;\n }\n\n /**\n * Visits each diffable block under `root`: top-level elements, but descending\n * into `<ul>`/`<ol>` (per `<li>`) and `<table>` (per `<tr>`).\n */\n private eachLogicalBlock(root: Element, visit: (el: Element) => void): void {\n for (const node of Array.from(root.childNodes)) {\n if (node.nodeType !== 1) continue; // elements only (skips code placeholders)\n const el = node as Element;\n const tag = el.tagName;\n if (tag === 'UL' || tag === 'OL') {\n for (const li of Array.from(el.children)) {\n if (li.tagName === 'LI') visit(li);\n }\n } else if (tag === 'TABLE' || el.querySelector('table')) {\n // Tables (incl. the scroll wrapper div) diff per row.\n for (const tr of Array.from(el.querySelectorAll('tr'))) {\n visit(tr);\n }\n } else {\n visit(el);\n }\n }\n }\n\n private blockTextKey(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n }\n\n private htmlToElement(html: string): HTMLElement | null {\n if (typeof document === 'undefined') return null;\n const div = document.createElement('div');\n div.innerHTML = html;\n return div;\n }\n\n private loadFromUrl(url: string): void {\n this.httpSub?.unsubscribe();\n this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({\n next: (text) => this.resolvedMarkdown.set(text),\n error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),\n });\n }\n\n private splitIntoParts(\n html: string,\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[]\n ): RenderPart[] {\n const parts: RenderPart[] = [];\n const regex = new RegExp(BLOCK_PLACEHOLDER.source, 'g');\n const cursor = { next: 0 };\n let lastIndex = 0;\n let partIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(html)) !== null) {\n const before = html.slice(lastIndex, match.index);\n if (before.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(before, cursor)),\n });\n }\n const block = blocks[parseInt(match[1], 10)];\n lastIndex = match.index + match[0].length;\n // Unforgeable placeholder, so this can only be a bug — but never a crash.\n if (!block) continue;\n if (block.kind === 'mermaid') {\n parts.push({\n type: 'mermaid',\n index: partIndex++,\n code: block.code,\n });\n } else {\n parts.push({\n type: 'code',\n index: partIndex++,\n code: block.code,\n lang: block.lang as CodeBlockLanguage,\n });\n }\n }\n\n const remaining = html.slice(lastIndex);\n if (remaining.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(remaining, cursor)),\n });\n }\n\n return parts;\n }\n\n private parseMarkdown(md: string): {\n html: string;\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[];\n headings: MarkdownHeading[];\n } {\n const blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[] = [];\n const headings: MarkdownHeading[] = [];\n const baseUrl = this.baseUrl();\n const linkTarget = this.linkTarget();\n const showAnchors = this.showHeadingAnchors();\n\n // 1. Extract fenced code blocks → placeholders (see BLOCK_MARK: the source\n // is stripped of the marker character first, so it can't forge one)\n // eslint-disable-next-line no-control-regex -- strip the placeholder marker (see BLOCK_MARK)\n let processed = md.replace(/\\u0000/g, '').replace(\n /```(\\w*)\\n([\\s\\S]*?)```/g,\n (_match, lang: string, code: string) => {\n const idx = blocks.length;\n const normalizedLang = (lang || 'text').toLowerCase();\n blocks.push({\n kind: normalizedLang === 'mermaid' ? 'mermaid' : 'code',\n lang: normalizedLang,\n code: code.trimEnd(),\n });\n return blockPlaceholder(idx);\n }\n );\n\n // 2. Escape HTML entities — quotes included, so nothing the parser later\n // interpolates into an attribute (link/image URLs, alt text) can close it.\n processed = this.escapeHtml(processed);\n\n // 3. Headings (# to ######)\n processed = processed.replace(\n /^(#{1,6})\\s+(.+)$/gm,\n (_match, hashes: string, text: string) => {\n const level = hashes.length;\n const id = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-');\n headings.push({ level, text, id });\n const anchor = showAnchors\n ? `<a href=\"#${id}\" class=\"lc-markdown__anchor\" aria-hidden=\"true\">#</a>`\n : '';\n return `<h${level} id=\"${id}\">${anchor}${text}</h${level}>`;\n }\n );\n\n // 4. Horizontal rules\n processed = processed.replace(/^---+$/gm, '<hr>');\n\n // 5. Blockquotes\n processed = processed.replace(\n /^(?:>)\\s?(.*)$/gm,\n '<blockquote>$1</blockquote>'\n );\n // Merge consecutive blockquotes\n processed = processed.replace(\n /<\\/blockquote>\\n<blockquote>/g,\n '\\n'\n );\n\n // 6. Tables (GFM)\n processed = this.parseTables(processed);\n\n // 7. Task lists. Angular's HTML sanitizer strips <input>, so the checkbox is\n // rendered as a styled <span role=\"checkbox\"> (which survives sanitization and\n // carries accessible checked/disabled state) rather than a form control.\n processed = processed.replace(\n /^[-*]\\s+\\[[xX]\\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox lc-markdown__checkbox--checked\" role=\"checkbox\" aria-checked=\"true\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n processed = processed.replace(\n /^[-*]\\s+\\[ \\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox\" role=\"checkbox\" aria-checked=\"false\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n\n // 8. Unordered lists\n processed = processed.replace(\n /^[-*]\\s+(.*)$/gm,\n '<li>$1</li>'\n );\n // Wrap consecutive <li> in <ul>\n processed = processed.replace(\n /((?:<li[^>]*>.*<\\/li>\\n?)+)/g,\n '<ul>$1</ul>'\n );\n\n // 9. Ordered lists — marked while wrapping, so a run of them gets its <ol>\n // and isn't confused with the <li>s already inside <ul>s above.\n processed = processed.replace(\n /^\\d+\\.\\s+(.*)$/gm,\n '<li data-ol>$1</li>'\n );\n processed = processed.replace(\n /((?:<li data-ol>.*<\\/li>\\n?)+)/g,\n (run) => `<ol>${run.replace(/ data-ol/g, '')}</ol>`\n );\n\n // 10. Inline code\n processed = processed.replace(\n /`([^`]+)`/g,\n '<code>$1</code>'\n );\n\n // 11. Bold + italic combos\n processed = processed.replace(\n /\\*\\*\\*(.+?)\\*\\*\\*/g,\n '<strong><em>$1</em></strong>'\n );\n processed = processed.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n processed = processed.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 12. Strikethrough (GFM)\n processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // 13. Images. Only allow-listed URL schemes become a `src` (see\n // safe-url.ts); anything else — `javascript:`, unknown schemes — is\n // rendered as its alt text. This holds regardless of `sanitize`.\n processed = processed.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_match, alt: string, src: string) => {\n if (!isSafeImageUrl(src)) return alt;\n const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;\n return `<img src=\"${resolvedSrc}\" alt=\"${alt}\" class=\"lc-markdown__img\">`;\n }\n );\n\n // 14. Links — same rule: an unsafe URL leaves plain text, not a link.\n processed = processed.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, href: string) => {\n if (!isSafeLinkUrl(href)) return text;\n const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')\n ? `${baseUrl}/${href}`\n : href;\n return `<a href=\"${resolvedHref}\" target=\"${linkTarget}\" rel=\"noopener\">${text}</a>`;\n }\n );\n\n // 14b. Autolinks — linkify bare URLs / www / emails in plain text. Runs after\n // explicit links and inline code so it can skip anything already inside an\n // <a> or <code> (and never touches tag attributes).\n processed = this.autolink(processed, linkTarget);\n\n // 15. Paragraphs — wrap standalone lines\n processed = processed\n .split('\\n\\n')\n .map((block) => {\n const trimmed = block.trim();\n if (!trimmed) return '';\n if (\n trimmed.startsWith('<h') ||\n trimmed.startsWith('<ul') ||\n trimmed.startsWith('<ol') ||\n trimmed.startsWith('<blockquote') ||\n trimmed.startsWith('<hr') ||\n trimmed.startsWith('<table') ||\n trimmed.startsWith('<div') ||\n trimmed.startsWith(BLOCK_MARK)\n ) {\n return trimmed;\n }\n return `<p>${trimmed.replace(/\\n/g, '<br>')}</p>`;\n })\n .join('\\n');\n\n // 16. Restore code block placeholders\n if (this.syntaxHighlight()) {\n // Leave placeholders — the template will render <lc-code-block> for each\n // No replacement needed here\n } else {\n processed = processed.replace(\n new RegExp(BLOCK_PLACEHOLDER.source, 'g'),\n (_match, idx: string) => {\n const block = blocks[parseInt(idx, 10)];\n if (!block) return '';\n return `<pre><code class=\"language-${block.lang}\">${this.escapeHtml(block.code)}</code></pre>`;\n }\n );\n }\n\n return { html: processed, blocks, headings };\n }\n\n private parseTables(text: string): string {\n return text.replace(\n /^(\\|.+\\|)\\n(\\|[-| :]+\\|)\\n((?:\\|.+\\|\\n?)*)/gm,\n (_match, header: string, separator: string, body: string) => {\n // Per-column alignment from the delimiter row: :--- left, :--: center,\n // ---: right. Surfaced as classes (the sanitizer strips inline `style`).\n const aligns = this.splitTableRow(separator).map((cell) => {\n const left = cell.startsWith(':');\n const right = cell.endsWith(':');\n if (left && right) return 'center';\n if (right) return 'right';\n if (left) return 'left';\n return '';\n });\n const alignClass = (col: number): string =>\n aligns[col] ? ` class=\"lc-markdown__cell--${aligns[col]}\"` : '';\n\n const headers = this.splitTableRow(header)\n .map((cell, i) => `<th scope=\"col\"${alignClass(i)}>${cell.trim()}</th>`)\n .join('');\n\n const rows = body\n .trim()\n .split('\\n')\n .map((row: string) => {\n const cells = this.splitTableRow(row)\n .map((cell, i) => `<td${alignClass(i)}>${cell.trim()}</td>`)\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n // Wrapped so wide tables scroll horizontally instead of breaking layout.\n return (\n `<div class=\"lc-markdown__table-wrap\">` +\n `<table class=\"lc-markdown__table\"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>` +\n `</div>`\n );\n }\n );\n }\n\n /** Splits a pipe-table row into trimmed cells, dropping the leading/trailing edges. */\n private splitTableRow(row: string): string[] {\n return row\n .trim()\n .replace(/^\\|/, '')\n .replace(/\\|$/, '')\n .split('|')\n .map((cell) => cell.trim());\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n }\n\n /**\n * Linkifies bare URLs (`http(s)://…`, `www.…`) and email addresses in text,\n * leaving anything inside an existing `<a>` or `<code>` (and all tag\n * attributes) untouched. The HTML is tokenized into tags vs. text so only\n * top-level text runs are linkified.\n */\n private autolink(html: string, linkTarget: string): string {\n const url = /(?:https?:\\/\\/|www\\.)[^\\s<]*[^\\s<.,:;!?)\\]}'\"]/;\n const email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/;\n const pattern = new RegExp(`${url.source}|${email.source}`, 'g');\n\n let depth = 0; // inside <a> or <code>\n return html\n .split(/(<[^>]+>)/g)\n .map((segment) => {\n if (segment.startsWith('<')) {\n if (/^<(a|code)[\\s>]/i.test(segment)) depth++;\n else if (/^<\\/(a|code)>/i.test(segment)) depth = Math.max(0, depth - 1);\n return segment;\n }\n if (depth > 0 || !segment) return segment;\n return segment.replace(pattern, (token) => {\n const isEmail = token.includes('@') && !/^(?:https?:\\/\\/|www\\.)/.test(token);\n const href = isEmail\n ? `mailto:${token}`\n : token.startsWith('www.')\n ? `https://${token}`\n : token;\n return `<a href=\"${href}\" target=\"${linkTarget}\" rel=\"noopener\">${token}</a>`;\n });\n })\n .join('');\n }\n\n protected mermaidSvgFor(index: number): SafeHtml | null {\n return this.mermaidSvgs()[index] ?? null;\n }\n\n protected mermaidErrorFor(index: number): string | null {\n return this.mermaidErrors()[index] ?? null;\n }\n\n private async renderMermaidParts(parts: RenderPart[]): Promise<void> {\n const mermaidParts = parts.filter((part) => part.type === 'mermaid' && !!part.code);\n this.mermaidSvgs.set({});\n this.mermaidErrors.set({});\n\n if (mermaidParts.length === 0) {\n return;\n }\n\n const runId = ++this.mermaidRenderRun;\n\n try {\n const mermaidApi = await this.getMermaidApi();\n if (runId !== this.mermaidRenderRun) return;\n\n const nextSvgs: Record<number, SafeHtml> = {};\n const nextErrors: Record<number, string> = {};\n\n for (const part of mermaidParts) {\n try {\n // Per instance, per run, per part: mermaid's render() removes any\n // element already carrying the id it is given, so an id shared with\n // another <lc-markdown> on the page would delete that one's diagram.\n const renderId = `lc-md-mermaid-${this.instanceId}-${runId}-${part.index}`;\n const { svg } = await mermaidApi.render(renderId, part.code!);\n if (runId !== this.mermaidRenderRun) return;\n // Mermaid emits self-contained SVG with internal <style> rules.\n // Angular HTML sanitization strips critical style declarations,\n // which leaves only labels visible (no edges/shapes). We trust\n // Mermaid output here and rely on Mermaid's strict security mode.\n nextSvgs[part.index] = this.sanitizer.bypassSecurityTrustHtml(svg);\n } catch {\n nextErrors[part.index] = this.mermaidErrorLabel();\n }\n }\n\n if (runId !== this.mermaidRenderRun) return;\n this.mermaidSvgs.set(nextSvgs);\n this.mermaidErrors.set(nextErrors);\n } catch {\n if (runId !== this.mermaidRenderRun) return;\n const fallbackErrors: Record<number, string> = {};\n for (const part of mermaidParts) {\n fallbackErrors[part.index] = this.mermaidUnavailableLabel();\n }\n this.mermaidErrors.set(fallbackErrors);\n }\n }\n\n private async getMermaidApi(): Promise<any> {\n if (!this.mermaidApiPromise) {\n this.mermaidApiPromise = import('mermaid').then((module) => {\n const api = module.default ?? module;\n if (!this.mermaidInitialized) {\n api.initialize({\n startOnLoad: false,\n securityLevel: 'strict',\n });\n this.mermaidInitialized = true;\n }\n return api;\n });\n }\n return this.mermaidApiPromise;\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- delegated handler for clicks on rendered links, which are focusable anchors -->\n<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @if (highlightChanges()) {\n <div class=\"lc-markdown__sr-only\" role=\"status\" aria-live=\"polite\">{{ changeSummary() }}</div>\n }\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else if (part.type === 'code') {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"part.lang ?? 'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n } @else {\n @if (mermaidSvgFor(part.index); as svg) {\n <div class=\"lc-markdown__mermaid\" [innerHTML]=\"svg\"></div>\n } @else if (mermaidErrorFor(part.index); as errorMessage) {\n <div class=\"lc-markdown__mermaid-fallback\">\n <p class=\"lc-markdown__mermaid-error\">{{ errorMessage }}</p>\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n </div>\n } @else {\n <div class=\"lc-markdown__mermaid-loading\">{{ mermaidLoadingLabel() }}</div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-markdown.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.ts","../../../../libs/angular-ui-kit/src/lib/markdown/markdown.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n signal,\n effect,\n untracked,\n inject,\n ElementRef,\n OnDestroy,\n SecurityContext,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { HttpClient } from '@angular/common/http';\nimport { Subscription } from 'rxjs';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport interface MarkdownHeading {\n level: number;\n text: string;\n id: string;\n}\n\nexport interface MarkdownLinkClick {\n href: string;\n event: MouseEvent;\n}\n\nexport interface MarkdownRendered {\n headings: MarkdownHeading[];\n}\n\nexport interface MarkdownChangesHighlighted {\n /** Number of changed/added blocks highlighted in the current render. */\n changedBlocks: number;\n}\n\nexport interface RenderPart {\n type: 'html' | 'code' | 'mermaid';\n index: number;\n safeHtml?: SafeHtml;\n code?: string;\n lang?: CodeBlockLanguage;\n}\n\n/**\n * Stand-in for a fenced code block while the rest of the markdown is processed.\n * Bracketed in NUL characters, which the source is stripped of first — so no\n * text a user writes can ever spell a placeholder and be mistaken for a block.\n * (The earlier `<!--CODE_BLOCK_N-->` form was: an escaped literal comment in\n * the text matched it and dereferenced a block that wasn't there.)\n */\nconst BLOCK_MARK = '\\u0000';\nconst blockPlaceholder = (idx: number): string => `${BLOCK_MARK}LCBLOCK${idx}${BLOCK_MARK}`;\n// eslint-disable-next-line no-control-regex -- the NUL bracket is the point: unforgeable\nconst BLOCK_PLACEHOLDER = /\\u0000LCBLOCK(\\d+)\\u0000/g;\n\n/** Instances numbered so their mermaid render ids never collide on one page. */\nlet markdownInstances = 0;\n\n/**\n * Markdown renderer component.\n *\n * Renders GitHub-Flavored Markdown (GFM) to sanitized HTML with\n * optional syntax highlighting via `<lc-code-block>`.\n *\n * Supports headings (+ anchors), paragraphs, ordered/unordered lists, task lists\n * (`- [ ]` / `- [x]` → accessible checkbox), blockquotes, horizontal rules,\n * images, GFM tables (column alignment + horizontal scroll), bold/italic/\n * strikethrough, inline code, fenced code (incl. `mermaid`), explicit links and\n * autolinks (bare URLs / `www.` / emails, never inside code).\n *\n * Optionally highlights *changed* blocks in place: pass the pre-edit markdown as\n * `previousContent` and set `highlightChanges` — added/edited blocks (diffed at\n * block / list-item level) gain a left accent bar + subtle tint, can auto-fade\n * (`changeHighlightFadeMs`) and scroll into view (`scrollToFirstChange`).\n *\n * @example\n * ```html\n * <lc-markdown [content]=\"'# Hello World'\" />\n * <lc-markdown [src]=\"'/docs/readme.md'\" />\n * <lc-markdown\n * [content]=\"current\" [previousContent]=\"prev\"\n * [highlightChanges]=\"true\" [changeHighlightFadeMs]=\"3000\" />\n * ```\n */\n@Component({\n selector: 'lc-markdown',\n standalone: true,\n imports: [CodeBlockComponent],\n templateUrl: './markdown.component.html',\n styleUrls: ['./markdown.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class MarkdownComponent implements OnDestroy {\n private readonly sanitizer = inject(DomSanitizer);\n private readonly instanceId = ++markdownInstances;\n private readonly http = inject(HttpClient);\n private readonly host = inject(ElementRef<HTMLElement>);\n private httpSub?: Subscription;\n private fadeTimer?: ReturnType<typeof setTimeout>;\n private scrollTimer?: ReturnType<typeof setTimeout>;\n private mermaidApiPromise?: Promise<any>;\n private mermaidInitialized = false;\n private mermaidRenderRun = 0;\n\n /** URL or path to load markdown from */\n readonly src = input<string>();\n\n /** Raw markdown string */\n readonly content = input<string>();\n\n /** Display variant */\n readonly variant = input<'default' | 'compact' | 'chat'>('default');\n\n /** Target for links */\n readonly linkTarget = input<'_self' | '_blank'>('_self');\n\n /** Whether to sanitize HTML output */\n readonly sanitize = input<boolean>(true);\n\n /** Whether to use code-block for fenced code */\n readonly syntaxHighlight = input<boolean>(true);\n\n /** Whether to show anchor links on headings */\n readonly showHeadingAnchors = input<boolean>(false);\n\n /** Base URL for resolving relative links/images */\n readonly baseUrl = input<string>();\n\n // -- Change highlighting --\n /** Enable change highlighting (requires `previousContent` to compute a diff). */\n readonly highlightChanges = input<boolean>(false);\n\n /**\n * The prior Markdown. When it differs from `content`, the changed/added blocks\n * in `content` are highlighted. The caller passes the pre-edit markdown.\n */\n readonly previousContent = input<string>();\n\n /** Auto-fade the highlight after N ms. 0 / undefined ⇒ persist until content changes. */\n readonly changeHighlightFadeMs = input<number>();\n\n /** Scroll the first changed block into view when highlights appear. */\n readonly scrollToFirstChange = input<boolean>(false);\n\n /** Screen-reader marker put in front of each highlighted block (`highlightChanges`). */\n readonly changedMarkerLabel = input('(geändert)');\n\n /** Announced after the count when one block changed: `1 Abschnitt geändert`. */\n readonly changedSummaryOneLabel = input('Abschnitt geändert');\n\n /** Announced after the count when several blocks changed: `3 Abschnitte geändert`. */\n readonly changedSummaryManyLabel = input('Abschnitte geändert');\n\n /** Shown while a Mermaid diagram renders. */\n readonly mermaidLoadingLabel = input('Rendering Mermaid diagram...');\n\n /** Shown above the source when a Mermaid diagram fails. */\n readonly mermaidErrorLabel = input('Mermaid diagram could not be rendered.');\n\n /** Shown above the source when the optional `mermaid` package cannot be loaded. */\n readonly mermaidUnavailableLabel = input('Mermaid runtime is not available.');\n\n /** Emitted when a link is clicked */\n readonly linkClick = output<MarkdownLinkClick>();\n\n /** Emitted after rendering with heading TOC */\n readonly rendered = output<MarkdownRendered>();\n\n /** Emitted after a render that produced change highlights. */\n readonly changesHighlighted = output<MarkdownChangesHighlighted>();\n\n /** Internal resolved markdown source */\n protected resolvedMarkdown = signal<string>('');\n\n /** Parsed result (computed once from resolvedMarkdown) */\n private parsed = computed(() => {\n const md = this.resolvedMarkdown();\n if (!md) {\n return {\n html: '',\n blocks: [] as { kind: 'code' | 'mermaid'; lang: string; code: string }[],\n headings: [] as MarkdownHeading[],\n };\n }\n return this.parseMarkdown(md);\n });\n\n /**\n * Rendered HTML after applying change highlights. When highlighting is off (or\n * there is no differing `previousContent`) this returns the parsed HTML\n * unchanged, so the non-highlight path is byte-for-byte identical to before.\n */\n private readonly highlightResult = computed<{ html: string; count: number }>(() => {\n const { html } = this.parsed();\n const prev = this.previousContent();\n if (\n !html ||\n !this.highlightChanges() ||\n prev == null ||\n prev === this.resolvedMarkdown()\n ) {\n return { html, count: 0 };\n }\n return this.applyChangeHighlights(html, prev);\n });\n\n /** Number of changed/added blocks highlighted in the current render. */\n protected readonly changedCount = computed(() => this.highlightResult().count);\n\n /** Visually-hidden polite summary announced when highlights appear. */\n protected readonly changeSummary = computed(() => {\n const n = this.changedCount();\n if (n <= 0) return '';\n return `${n} ${n === 1 ? this.changedSummaryOneLabel() : this.changedSummaryManyLabel()}`;\n });\n\n /** Whether the current highlights have faded out (after `changeHighlightFadeMs`). */\n protected readonly highlightsFaded = signal(false);\n\n /** Computed render parts (HTML chunks + code blocks interleaved) */\n protected renderParts = computed<RenderPart[]>(() => {\n const html = this.highlightResult().html;\n const { blocks } = this.parsed();\n if (!html) return [];\n\n if (this.syntaxHighlight() && blocks.length > 0) {\n return this.splitIntoParts(html, blocks);\n }\n\n // No code blocks — single HTML part\n return [{\n type: 'html',\n index: 0,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(html, { next: 0 })),\n }];\n });\n\n /**\n * The HTML as it may go into the DOM. With `sanitize` on (the default) that is\n * Angular's HTML sanitizer's output — which drops every `id`, since `id` isn't\n * on its attribute allow-list. The heading ids are put back afterwards: they\n * are what anchors and the `rendered` TOC point at, and each is a `[\\w-]`\n * slug this parser produced itself, so re-adding them reopens nothing. The\n * cursor walks the parsed headings in document order across the parts.\n */\n private sanitizeHtml(html: string, cursor: { next: number }): string {\n const headings = this.headings();\n if (!this.sanitize()) {\n cursor.next += (html.match(/<h[1-6]\\b/g) ?? []).length;\n return html;\n }\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html) || '';\n return sanitized.replace(/<h([1-6])(\\s[^>]*)?>/g, (tag, level: string, attrs = '') => {\n const heading = headings[cursor.next++];\n if (!heading || /\\sid=/.test(attrs)) return tag;\n return `<h${level} id=\"${heading.id}\"${attrs}>`;\n });\n }\n\n protected containerClasses = computed(() => {\n let classes = `lc-markdown lc-markdown--${this.variant()}`;\n if (this.highlightsFaded()) classes += ' lc-markdown--faded';\n return classes;\n });\n\n protected mermaidSvgs = signal<Record<number, SafeHtml>>({});\n protected mermaidErrors = signal<Record<number, string>>({});\n\n private headings = computed(() => this.parsed().headings);\n\n constructor() {\n // Load content from src when src changes\n effect(() => {\n const src = this.src();\n if (src) {\n this.loadFromUrl(src);\n }\n });\n\n // Use raw content when provided\n effect(() => {\n const content = this.content();\n if (content !== undefined) {\n this.resolvedMarkdown.set(content);\n }\n });\n\n // Emit rendered event whenever a render produced a (possibly empty) TOC\n effect(() => {\n const h = this.headings();\n untracked(() => this.rendered.emit({ headings: h }));\n });\n\n // Render Mermaid code fences as inline SVG diagrams.\n effect(() => {\n void this.renderMermaidParts(this.renderParts());\n });\n\n // React to a render that produced change highlights: emit, schedule fade,\n // and optionally scroll the first changed block into view.\n effect(() => {\n const count = this.highlightResult().count;\n untracked(() => this.onHighlightResult(count));\n });\n }\n\n ngOnDestroy(): void {\n this.httpSub?.unsubscribe();\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n }\n\n protected onLinkClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (anchor) {\n this.linkClick.emit({ href: anchor.href, event });\n }\n }\n\n // -- Change highlighting -----------------------------------------------------\n\n /** Side-effects for a render: emit, (re)arm the fade timer, optional scroll. */\n private onHighlightResult(count: number): void {\n clearTimeout(this.fadeTimer);\n clearTimeout(this.scrollTimer);\n // A fresh render starts un-faded (re-entrant: never reuse stale fade state).\n this.highlightsFaded.set(false);\n if (count <= 0) return;\n\n this.changesHighlighted.emit({ changedBlocks: count });\n\n const fadeMs = this.changeHighlightFadeMs();\n if (fadeMs && fadeMs > 0) {\n this.fadeTimer = setTimeout(() => this.highlightsFaded.set(true), fadeMs);\n }\n if (this.scrollToFirstChange()) {\n // Defer until the highlighted HTML has been written to the DOM.\n this.scrollTimer = setTimeout(() => this.scrollToFirstChanged(), 0);\n }\n }\n\n private scrollToFirstChanged(): void {\n if (typeof document === 'undefined') return;\n const el = this.host.nativeElement.querySelector('.lc-markdown__block--changed');\n el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n /**\n * Wraps the changed/added top-level blocks of `html` with\n * `.lc-markdown__block--changed`. A block is \"changed\" when its normalized\n * text is not present among the blocks of `prevMarkdown`. Lists are diffed\n * per `<li>` and tables per `<tr>` so a single edited item highlights alone.\n */\n private applyChangeHighlights(\n html: string,\n prevMarkdown: string\n ): { html: string; count: number } {\n if (typeof document === 'undefined') return { html, count: 0 };\n\n const prevKeys = this.collectBlockKeys(this.parseMarkdown(prevMarkdown).html);\n const root = this.htmlToElement(html);\n if (!root) return { html, count: 0 };\n\n let count = 0;\n const markIfChanged = (el: Element): void => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (!key || prevKeys.has(key)) return;\n el.classList.add('lc-markdown__block--changed');\n const sr = document.createElement('span');\n sr.className = 'lc-markdown__sr-only';\n sr.textContent = `${this.changedMarkerLabel()} `;\n el.insertBefore(sr, el.firstChild);\n count++;\n };\n\n this.eachLogicalBlock(root, markIfChanged);\n return { html: root.innerHTML, count };\n }\n\n /** Normalized text set of the logical blocks within an HTML fragment. */\n private collectBlockKeys(html: string): Set<string> {\n const keys = new Set<string>();\n const root = this.htmlToElement(html);\n if (!root) return keys;\n this.eachLogicalBlock(root, (el) => {\n const key = this.blockTextKey(el.textContent ?? '');\n if (key) keys.add(key);\n });\n return keys;\n }\n\n /**\n * Visits each diffable block under `root`: top-level elements, but descending\n * into `<ul>`/`<ol>` (per `<li>`) and `<table>` (per `<tr>`).\n */\n private eachLogicalBlock(root: Element, visit: (el: Element) => void): void {\n for (const node of Array.from(root.childNodes)) {\n if (node.nodeType !== 1) continue; // elements only (skips code placeholders)\n const el = node as Element;\n const tag = el.tagName;\n if (tag === 'UL' || tag === 'OL') {\n for (const li of Array.from(el.children)) {\n if (li.tagName === 'LI') visit(li);\n }\n } else if (tag === 'TABLE' || el.querySelector('table')) {\n // Tables (incl. the scroll wrapper div) diff per row.\n for (const tr of Array.from(el.querySelectorAll('tr'))) {\n visit(tr);\n }\n } else {\n visit(el);\n }\n }\n }\n\n private blockTextKey(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().toLowerCase();\n }\n\n private htmlToElement(html: string): HTMLElement | null {\n if (typeof document === 'undefined') return null;\n const div = document.createElement('div');\n div.innerHTML = html;\n return div;\n }\n\n private loadFromUrl(url: string): void {\n this.httpSub?.unsubscribe();\n this.httpSub = this.http.get(url, { responseType: 'text' }).subscribe({\n next: (text) => this.resolvedMarkdown.set(text),\n error: () => this.resolvedMarkdown.set(`*Failed to load ${url}*`),\n });\n }\n\n private splitIntoParts(\n html: string,\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[]\n ): RenderPart[] {\n const parts: RenderPart[] = [];\n const regex = new RegExp(BLOCK_PLACEHOLDER.source, 'g');\n const cursor = { next: 0 };\n let lastIndex = 0;\n let partIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(html)) !== null) {\n const before = html.slice(lastIndex, match.index);\n if (before.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(before, cursor)),\n });\n }\n const block = blocks[parseInt(match[1], 10)];\n lastIndex = match.index + match[0].length;\n // Unforgeable placeholder, so this can only be a bug — but never a crash.\n if (!block) continue;\n if (block.kind === 'mermaid') {\n parts.push({\n type: 'mermaid',\n index: partIndex++,\n code: block.code,\n });\n } else {\n parts.push({\n type: 'code',\n index: partIndex++,\n code: block.code,\n lang: block.lang as CodeBlockLanguage,\n });\n }\n }\n\n const remaining = html.slice(lastIndex);\n if (remaining.trim()) {\n parts.push({\n type: 'html',\n index: partIndex++,\n safeHtml: this.sanitizer.bypassSecurityTrustHtml(this.sanitizeHtml(remaining, cursor)),\n });\n }\n\n return parts;\n }\n\n private parseMarkdown(md: string): {\n html: string;\n blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[];\n headings: MarkdownHeading[];\n } {\n const blocks: { kind: 'code' | 'mermaid'; lang: string; code: string }[] = [];\n const headings: MarkdownHeading[] = [];\n const baseUrl = this.baseUrl();\n const linkTarget = this.linkTarget();\n const showAnchors = this.showHeadingAnchors();\n\n // 1. Extract fenced code blocks → placeholders (see BLOCK_MARK: the source\n // is stripped of the marker character first, so it can't forge one)\n // eslint-disable-next-line no-control-regex -- strip the placeholder marker (see BLOCK_MARK)\n let processed = md.replace(/\\u0000/g, '').replace(\n /```(\\w*)\\n([\\s\\S]*?)```/g,\n (_match, lang: string, code: string) => {\n const idx = blocks.length;\n const normalizedLang = (lang || 'text').toLowerCase();\n blocks.push({\n kind: normalizedLang === 'mermaid' ? 'mermaid' : 'code',\n lang: normalizedLang,\n code: code.trimEnd(),\n });\n return blockPlaceholder(idx);\n }\n );\n\n // 2. Escape HTML entities — quotes included, so nothing the parser later\n // interpolates into an attribute (link/image URLs, alt text) can close it.\n processed = this.escapeHtml(processed);\n\n // 3. Headings (# to ######)\n processed = processed.replace(\n /^(#{1,6})\\s+(.+)$/gm,\n (_match, hashes: string, text: string) => {\n const level = hashes.length;\n const id = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-');\n headings.push({ level, text, id });\n const anchor = showAnchors\n ? `<a href=\"#${id}\" class=\"lc-markdown__anchor\" aria-hidden=\"true\">#</a>`\n : '';\n return `<h${level} id=\"${id}\">${anchor}${text}</h${level}>`;\n }\n );\n\n // 4. Horizontal rules\n processed = processed.replace(/^---+$/gm, '<hr>');\n\n // 5. Blockquotes\n processed = processed.replace(\n /^(?:>)\\s?(.*)$/gm,\n '<blockquote>$1</blockquote>'\n );\n // Merge consecutive blockquotes\n processed = processed.replace(\n /<\\/blockquote>\\n<blockquote>/g,\n '\\n'\n );\n\n // 6. Tables (GFM)\n processed = this.parseTables(processed);\n\n // 7. Task lists. Angular's HTML sanitizer strips <input>, so the checkbox is\n // rendered as a styled <span role=\"checkbox\"> (which survives sanitization and\n // carries accessible checked/disabled state) rather than a form control.\n processed = processed.replace(\n /^[-*]\\s+\\[[xX]\\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox lc-markdown__checkbox--checked\" role=\"checkbox\" aria-checked=\"true\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n processed = processed.replace(\n /^[-*]\\s+\\[ \\]\\s+(.*)$/gm,\n '<li class=\"lc-markdown__task-item\">' +\n '<span class=\"lc-markdown__checkbox\" role=\"checkbox\" aria-checked=\"false\" aria-disabled=\"true\"></span>' +\n '<span class=\"lc-markdown__task-label\">$1</span></li>'\n );\n\n // 8. Unordered lists\n processed = processed.replace(\n /^[-*]\\s+(.*)$/gm,\n '<li>$1</li>'\n );\n // Wrap consecutive <li> in <ul>\n processed = processed.replace(\n /((?:<li[^>]*>.*<\\/li>\\n?)+)/g,\n '<ul>$1</ul>'\n );\n\n // 9. Ordered lists — marked while wrapping, so a run of them gets its <ol>\n // and isn't confused with the <li>s already inside <ul>s above.\n processed = processed.replace(\n /^\\d+\\.\\s+(.*)$/gm,\n '<li data-ol>$1</li>'\n );\n processed = processed.replace(\n /((?:<li data-ol>.*<\\/li>\\n?)+)/g,\n (run) => `<ol>${run.replace(/ data-ol/g, '')}</ol>`\n );\n\n // 10. Inline code\n processed = processed.replace(\n /`([^`]+)`/g,\n '<code>$1</code>'\n );\n\n // 11. Bold + italic combos\n processed = processed.replace(\n /\\*\\*\\*(.+?)\\*\\*\\*/g,\n '<strong><em>$1</em></strong>'\n );\n processed = processed.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n processed = processed.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 12. Strikethrough (GFM)\n processed = processed.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // 13. Images. Only allow-listed URL schemes become a `src` (see\n // safe-url.ts); anything else — `javascript:`, unknown schemes — is\n // rendered as its alt text. This holds regardless of `sanitize`.\n processed = processed.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_match, alt: string, src: string) => {\n if (!isSafeImageUrl(src)) return alt;\n const resolvedSrc = baseUrl && !src.startsWith('http') ? `${baseUrl}/${src}` : src;\n return `<img src=\"${resolvedSrc}\" alt=\"${alt}\" class=\"lc-markdown__img\">`;\n }\n );\n\n // 14. Links — same rule: an unsafe URL leaves plain text, not a link.\n processed = processed.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, href: string) => {\n if (!isSafeLinkUrl(href)) return text;\n const resolvedHref = baseUrl && !href.startsWith('http') && !href.startsWith('#')\n ? `${baseUrl}/${href}`\n : href;\n return `<a href=\"${resolvedHref}\" target=\"${linkTarget}\" rel=\"noopener\">${text}</a>`;\n }\n );\n\n // 14b. Autolinks — linkify bare URLs / www / emails in plain text. Runs after\n // explicit links and inline code so it can skip anything already inside an\n // <a> or <code> (and never touches tag attributes).\n processed = this.autolink(processed, linkTarget);\n\n // 15. Paragraphs — wrap standalone lines\n processed = processed\n .split('\\n\\n')\n .map((block) => {\n const trimmed = block.trim();\n if (!trimmed) return '';\n if (\n trimmed.startsWith('<h') ||\n trimmed.startsWith('<ul') ||\n trimmed.startsWith('<ol') ||\n trimmed.startsWith('<blockquote') ||\n trimmed.startsWith('<hr') ||\n trimmed.startsWith('<table') ||\n trimmed.startsWith('<div') ||\n trimmed.startsWith(BLOCK_MARK)\n ) {\n return trimmed;\n }\n return `<p>${trimmed.replace(/\\n/g, '<br>')}</p>`;\n })\n .join('\\n');\n\n // 16. Restore code block placeholders\n if (this.syntaxHighlight()) {\n // Leave placeholders — the template will render <lc-code-block> for each\n // No replacement needed here\n } else {\n processed = processed.replace(\n new RegExp(BLOCK_PLACEHOLDER.source, 'g'),\n (_match, idx: string) => {\n const block = blocks[parseInt(idx, 10)];\n if (!block) return '';\n return `<pre><code class=\"language-${block.lang}\">${this.escapeHtml(block.code)}</code></pre>`;\n }\n );\n }\n\n return { html: processed, blocks, headings };\n }\n\n private parseTables(text: string): string {\n return text.replace(\n /^(\\|.+\\|)\\n(\\|[-| :]+\\|)\\n((?:\\|.+\\|\\n?)*)/gm,\n (_match, header: string, separator: string, body: string) => {\n // Per-column alignment from the delimiter row: :--- left, :--: center,\n // ---: right. Surfaced as classes (the sanitizer strips inline `style`).\n const aligns = this.splitTableRow(separator).map((cell) => {\n const left = cell.startsWith(':');\n const right = cell.endsWith(':');\n if (left && right) return 'center';\n if (right) return 'right';\n if (left) return 'left';\n return '';\n });\n const alignClass = (col: number): string =>\n aligns[col] ? ` class=\"lc-markdown__cell--${aligns[col]}\"` : '';\n\n const headers = this.splitTableRow(header)\n .map((cell, i) => `<th scope=\"col\"${alignClass(i)}>${cell.trim()}</th>`)\n .join('');\n\n const rows = body\n .trim()\n .split('\\n')\n .map((row: string) => {\n const cells = this.splitTableRow(row)\n .map((cell, i) => `<td${alignClass(i)}>${cell.trim()}</td>`)\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n\n // Wrapped so wide tables scroll horizontally instead of breaking layout.\n return (\n `<div class=\"lc-markdown__table-wrap\">` +\n `<table class=\"lc-markdown__table\"><thead><tr>${headers}</tr></thead><tbody>${rows}</tbody></table>` +\n `</div>`\n );\n }\n );\n }\n\n /** Splits a pipe-table row into trimmed cells, dropping the leading/trailing edges. */\n private splitTableRow(row: string): string[] {\n return row\n .trim()\n .replace(/^\\|/, '')\n .replace(/\\|$/, '')\n .split('|')\n .map((cell) => cell.trim());\n }\n\n private escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n }\n\n /**\n * Linkifies bare URLs (`http(s)://…`, `www.…`) and email addresses in text,\n * leaving anything inside an existing `<a>` or `<code>` (and all tag\n * attributes) untouched. The HTML is tokenized into tags vs. text so only\n * top-level text runs are linkified.\n */\n private autolink(html: string, linkTarget: string): string {\n const url = /(?:https?:\\/\\/|www\\.)[^\\s<]*[^\\s<.,:;!?)\\]}'\"]/;\n const email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/;\n const pattern = new RegExp(`${url.source}|${email.source}`, 'g');\n\n let depth = 0; // inside <a> or <code>\n return html\n .split(/(<[^>]+>)/g)\n .map((segment) => {\n if (segment.startsWith('<')) {\n if (/^<(a|code)[\\s>]/i.test(segment)) depth++;\n else if (/^<\\/(a|code)>/i.test(segment)) depth = Math.max(0, depth - 1);\n return segment;\n }\n if (depth > 0 || !segment) return segment;\n return segment.replace(pattern, (token) => {\n const isEmail = token.includes('@') && !/^(?:https?:\\/\\/|www\\.)/.test(token);\n const href = isEmail\n ? `mailto:${token}`\n : token.startsWith('www.')\n ? `https://${token}`\n : token;\n return `<a href=\"${href}\" target=\"${linkTarget}\" rel=\"noopener\">${token}</a>`;\n });\n })\n .join('');\n }\n\n protected mermaidSvgFor(index: number): SafeHtml | null {\n return this.mermaidSvgs()[index] ?? null;\n }\n\n protected mermaidErrorFor(index: number): string | null {\n return this.mermaidErrors()[index] ?? null;\n }\n\n private async renderMermaidParts(parts: RenderPart[]): Promise<void> {\n const mermaidParts = parts.filter((part) => part.type === 'mermaid' && !!part.code);\n this.mermaidSvgs.set({});\n this.mermaidErrors.set({});\n\n if (mermaidParts.length === 0) {\n return;\n }\n\n const runId = ++this.mermaidRenderRun;\n\n try {\n const mermaidApi = await this.getMermaidApi();\n if (runId !== this.mermaidRenderRun) return;\n\n const nextSvgs: Record<number, SafeHtml> = {};\n const nextErrors: Record<number, string> = {};\n\n for (const part of mermaidParts) {\n try {\n // Per instance, per run, per part: mermaid's render() removes any\n // element already carrying the id it is given, so an id shared with\n // another <lc-markdown> on the page would delete that one's diagram.\n const renderId = `lc-md-mermaid-${this.instanceId}-${runId}-${part.index}`;\n const { svg } = await mermaidApi.render(renderId, part.code!);\n if (runId !== this.mermaidRenderRun) return;\n // Mermaid emits self-contained SVG with internal <style> rules.\n // Angular HTML sanitization strips critical style declarations,\n // which leaves only labels visible (no edges/shapes). We trust\n // Mermaid output here and rely on Mermaid's strict security mode.\n nextSvgs[part.index] = this.sanitizer.bypassSecurityTrustHtml(svg);\n } catch {\n nextErrors[part.index] = this.mermaidErrorLabel();\n }\n }\n\n if (runId !== this.mermaidRenderRun) return;\n this.mermaidSvgs.set(nextSvgs);\n this.mermaidErrors.set(nextErrors);\n } catch {\n if (runId !== this.mermaidRenderRun) return;\n const fallbackErrors: Record<number, string> = {};\n for (const part of mermaidParts) {\n fallbackErrors[part.index] = this.mermaidUnavailableLabel();\n }\n this.mermaidErrors.set(fallbackErrors);\n }\n }\n\n private async getMermaidApi(): Promise<any> {\n if (!this.mermaidApiPromise) {\n this.mermaidApiPromise = import('mermaid').then((module) => {\n const api = module.default ?? module;\n if (!this.mermaidInitialized) {\n api.initialize({\n startOnLoad: false,\n securityLevel: 'strict',\n });\n this.mermaidInitialized = true;\n }\n return api;\n });\n }\n return this.mermaidApiPromise;\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- delegated handler for clicks on rendered links, which are focusable anchors -->\n<div [class]=\"containerClasses()\" (click)=\"onLinkClick($event)\">\n @if (highlightChanges()) {\n <div class=\"lc-markdown__sr-only\" role=\"status\" aria-live=\"polite\">{{ changeSummary() }}</div>\n }\n @for (part of renderParts(); track part.index) {\n @if (part.type === 'html') {\n <div [innerHTML]=\"part.safeHtml\"></div>\n } @else if (part.type === 'code') {\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"part.lang ?? 'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n } @else {\n @if (mermaidSvgFor(part.index); as svg) {\n <div class=\"lc-markdown__mermaid\" [innerHTML]=\"svg\"></div>\n } @else if (mermaidErrorFor(part.index); as errorMessage) {\n <div class=\"lc-markdown__mermaid-fallback\">\n <p class=\"lc-markdown__mermaid-error\">{{ errorMessage }}</p>\n <lc-code-block\n [code]=\"part.code!\"\n [language]=\"'text'\"\n [showLineNumbers]=\"true\"\n [showCopy]=\"true\"\n />\n </div>\n } @else {\n <div class=\"lc-markdown__mermaid-loading\">{{ mermaidLoadingLabel() }}</div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -355,7 +355,7 @@ var NotificationCenterComponent = class NotificationCenterComponent {
|
|
|
355
355
|
},
|
|
356
356
|
ngImport: i0,
|
|
357
357
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
358
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-info-
|
|
358
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:none;border-radius:var(--border-radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"],
|
|
359
359
|
dependencies: [{
|
|
360
360
|
kind: "component",
|
|
361
361
|
type: IconComponent,
|
|
@@ -418,7 +418,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
418
418
|
])],
|
|
419
419
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
420
420
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
421
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-info-
|
|
421
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:8px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:none;border-radius:var(--border-radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"]
|
|
422
422
|
}]
|
|
423
423
|
}],
|
|
424
424
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
@@ -73,7 +73,7 @@ var PipelineComponent = class PipelineComponent {
|
|
|
73
73
|
outputs: { stepClick: "stepClick" },
|
|
74
74
|
ngImport: i0,
|
|
75
75
|
template: "<ol [class]=\"hostClass()\">\n @for (step of steps(); track $index; let last = $last) {\n <li [class]=\"stepClass(step)\" [class.lc-pipeline__step--last]=\"last\">\n @if (clickable()) {\n <button type=\"button\" class=\"lc-pipeline__hit\" (click)=\"onActivate(step)\">\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n </button>\n } @else {\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n }\n </li>\n }\n</ol>\n\n<ng-template #body let-step>\n <span class=\"lc-pipeline__marker\">\n <span class=\"lc-pipeline__connector\" aria-hidden=\"true\"></span>\n <span class=\"lc-pipeline__node\">\n @if (iconFor(step)) {\n <lc-icon [name]=\"iconFor(step)\" size=\"xs\" [decorative]=\"true\" />\n }\n </span>\n </span>\n <span class=\"lc-pipeline__text\">\n <span class=\"lc-pipeline__label\">{{ step.label }}</span>\n @if (step.caption) {\n <span class=\"lc-pipeline__caption\">{{ step.caption }}</span>\n }\n </span>\n</ng-template>\n",
|
|
76
|
-
styles: [":host{display:block}.lc-pipeline{--lc-pipeline-node-size: 1.75rem;--lc-pipeline-line-weight: 2px;--lc-pipeline-gap: var(--spacing-2);--lc-pipeline-line: var(--color-border-strong);--lc-pipeline-line-done: var(--color-primary-500);--lc-pipeline-node-bg: var(--color-surface);--lc-pipeline-node-fg: var(--color-text-tertiary);--lc-pipeline-node-border: var(--lc-pipeline-line)}.lc-pipeline--size-sm{--lc-pipeline-node-size: 1.25rem;--lc-pipeline-line-weight: 1px;--lc-pipeline-gap: var(--spacing-1)}.lc-pipeline--size-sm .lc-pipeline__label{font-size:var(--font-size-xs)}.lc-pipeline--size-sm .lc-pipeline__caption{font-size:var(--font-size-2xs, .6875rem)}.lc-pipeline--size-sm .lc-pipeline__node lc-icon{transform:scale(.85)}.lc-pipeline{list-style:none;margin:0;padding:0;display:flex}.lc-pipeline__step{position:relative;display:flex;min-width:0}.lc-pipeline__hit{display:flex;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-align:inherit;cursor:pointer;width:100%}.lc-pipeline__hit:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px;border-radius:var(--border-radius-sm)}.lc-pipeline__marker{flex-shrink:0}.lc-pipeline__node{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:var(--lc-pipeline-node-size);height:var(--lc-pipeline-node-size);border-radius:var(--border-radius-full);background-color:var(--lc-pipeline-node-bg);border:var(--lc-pipeline-line-weight) solid var(--lc-pipeline-node-border);color:var(--lc-pipeline-node-fg);box-sizing:border-box}.lc-pipeline__label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}.lc-pipeline__caption{display:block;font-size:var(--font-size-xs);color:var(--color-text-tertiary);line-height:1.35}.lc-pipeline--horizontal{align-items:flex-start;gap:var(--lc-pipeline-gap)}.lc-pipeline--horizontal .lc-pipeline__step{flex:1 1 0;flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__hit{flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__connector{position:absolute;top:calc(var(--lc-pipeline-node-size) / 2);left:calc(var(--lc-pipeline-node-size) / 2);width:calc(100% + var(--lc-pipeline-gap));height:var(--lc-pipeline-line-weight);transform:translateY(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--horizontal .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--horizontal .lc-pipeline__text{margin-top:var(--spacing-2);padding-right:var(--spacing-3)}.lc-pipeline--vertical{flex-direction:column;gap:0}.lc-pipeline--vertical .lc-pipeline__step{flex-direction:row;align-items:flex-start;gap:var(--spacing-3);padding-bottom:var(--spacing-4)}.lc-pipeline--vertical .lc-pipeline__hit{flex-direction:row;align-items:flex-start;gap:var(--spacing-3)}.lc-pipeline--vertical .lc-pipeline__connector{position:absolute;left:calc(var(--lc-pipeline-node-size) / 2);top:var(--lc-pipeline-node-size);width:var(--lc-pipeline-line-weight);height:calc(100% - var(--lc-pipeline-node-size));transform:translate(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--vertical .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--vertical .lc-pipeline__text{padding-top:calc((var(--lc-pipeline-node-size) - 1.25rem) / 2)}.lc-pipeline__step--complete .lc-pipeline__node{background-color:var(--color-primary-500);border-color:var(--color-primary-500);color:var(--color-
|
|
76
|
+
styles: [":host{display:block}.lc-pipeline{--lc-pipeline-node-size: 1.75rem;--lc-pipeline-line-weight: 2px;--lc-pipeline-gap: var(--spacing-2);--lc-pipeline-line: var(--color-border-strong);--lc-pipeline-line-done: var(--color-primary-500);--lc-pipeline-node-bg: var(--color-surface);--lc-pipeline-node-fg: var(--color-text-tertiary);--lc-pipeline-node-border: var(--lc-pipeline-line)}.lc-pipeline--size-sm{--lc-pipeline-node-size: 1.25rem;--lc-pipeline-line-weight: 1px;--lc-pipeline-gap: var(--spacing-1)}.lc-pipeline--size-sm .lc-pipeline__label{font-size:var(--font-size-xs)}.lc-pipeline--size-sm .lc-pipeline__caption{font-size:var(--font-size-2xs, .6875rem)}.lc-pipeline--size-sm .lc-pipeline__node lc-icon{transform:scale(.85)}.lc-pipeline{list-style:none;margin:0;padding:0;display:flex}.lc-pipeline__step{position:relative;display:flex;min-width:0}.lc-pipeline__hit{display:flex;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-align:inherit;cursor:pointer;width:100%}.lc-pipeline__hit:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px;border-radius:var(--border-radius-sm)}.lc-pipeline__marker{flex-shrink:0}.lc-pipeline__node{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:var(--lc-pipeline-node-size);height:var(--lc-pipeline-node-size);border-radius:var(--border-radius-full);background-color:var(--lc-pipeline-node-bg);border:var(--lc-pipeline-line-weight) solid var(--lc-pipeline-node-border);color:var(--lc-pipeline-node-fg);box-sizing:border-box}.lc-pipeline__label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}.lc-pipeline__caption{display:block;font-size:var(--font-size-xs);color:var(--color-text-tertiary);line-height:1.35}.lc-pipeline--horizontal{align-items:flex-start;gap:var(--lc-pipeline-gap)}.lc-pipeline--horizontal .lc-pipeline__step{flex:1 1 0;flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__hit{flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__connector{position:absolute;top:calc(var(--lc-pipeline-node-size) / 2);left:calc(var(--lc-pipeline-node-size) / 2);width:calc(100% + var(--lc-pipeline-gap));height:var(--lc-pipeline-line-weight);transform:translateY(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--horizontal .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--horizontal .lc-pipeline__text{margin-top:var(--spacing-2);padding-right:var(--spacing-3)}.lc-pipeline--vertical{flex-direction:column;gap:0}.lc-pipeline--vertical .lc-pipeline__step{flex-direction:row;align-items:flex-start;gap:var(--spacing-3);padding-bottom:var(--spacing-4)}.lc-pipeline--vertical .lc-pipeline__hit{flex-direction:row;align-items:flex-start;gap:var(--spacing-3)}.lc-pipeline--vertical .lc-pipeline__connector{position:absolute;left:calc(var(--lc-pipeline-node-size) / 2);top:var(--lc-pipeline-node-size);width:var(--lc-pipeline-line-weight);height:calc(100% - var(--lc-pipeline-node-size));transform:translate(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--vertical .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--vertical .lc-pipeline__text{padding-top:calc((var(--lc-pipeline-node-size) - 1.25rem) / 2)}.lc-pipeline__step--complete .lc-pipeline__node{background-color:var(--color-primary-500);border-color:var(--color-primary-500);color:var(--color-surface)}.lc-pipeline__step--complete .lc-pipeline__connector{background-color:var(--lc-pipeline-line-done)}.lc-pipeline__step--current .lc-pipeline__node{border-color:var(--color-primary-500);color:var(--color-primary-500);box-shadow:var(--shadow-brand-glow)}.lc-pipeline__step--current .lc-pipeline__connector{background-color:var(--lc-pipeline-line-done)}.lc-pipeline__step--warning .lc-pipeline__node{border-color:var(--color-on-warning-subtle);color:var(--color-on-warning-subtle)}.lc-pipeline__step--error .lc-pipeline__node{border-color:var(--color-on-error-subtle);color:var(--color-on-error-subtle)}.lc-pipeline__step--pending .lc-pipeline__node{border-style:dashed}.lc-pipeline__step--pending .lc-pipeline__label{color:var(--color-text-secondary)}\n"],
|
|
77
77
|
dependencies: [{
|
|
78
78
|
kind: "component",
|
|
79
79
|
type: IconComponent,
|
|
@@ -123,7 +123,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
123
123
|
])],
|
|
124
124
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
125
125
|
template: "<ol [class]=\"hostClass()\">\n @for (step of steps(); track $index; let last = $last) {\n <li [class]=\"stepClass(step)\" [class.lc-pipeline__step--last]=\"last\">\n @if (clickable()) {\n <button type=\"button\" class=\"lc-pipeline__hit\" (click)=\"onActivate(step)\">\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n </button>\n } @else {\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n }\n </li>\n }\n</ol>\n\n<ng-template #body let-step>\n <span class=\"lc-pipeline__marker\">\n <span class=\"lc-pipeline__connector\" aria-hidden=\"true\"></span>\n <span class=\"lc-pipeline__node\">\n @if (iconFor(step)) {\n <lc-icon [name]=\"iconFor(step)\" size=\"xs\" [decorative]=\"true\" />\n }\n </span>\n </span>\n <span class=\"lc-pipeline__text\">\n <span class=\"lc-pipeline__label\">{{ step.label }}</span>\n @if (step.caption) {\n <span class=\"lc-pipeline__caption\">{{ step.caption }}</span>\n }\n </span>\n</ng-template>\n",
|
|
126
|
-
styles: [":host{display:block}.lc-pipeline{--lc-pipeline-node-size: 1.75rem;--lc-pipeline-line-weight: 2px;--lc-pipeline-gap: var(--spacing-2);--lc-pipeline-line: var(--color-border-strong);--lc-pipeline-line-done: var(--color-primary-500);--lc-pipeline-node-bg: var(--color-surface);--lc-pipeline-node-fg: var(--color-text-tertiary);--lc-pipeline-node-border: var(--lc-pipeline-line)}.lc-pipeline--size-sm{--lc-pipeline-node-size: 1.25rem;--lc-pipeline-line-weight: 1px;--lc-pipeline-gap: var(--spacing-1)}.lc-pipeline--size-sm .lc-pipeline__label{font-size:var(--font-size-xs)}.lc-pipeline--size-sm .lc-pipeline__caption{font-size:var(--font-size-2xs, .6875rem)}.lc-pipeline--size-sm .lc-pipeline__node lc-icon{transform:scale(.85)}.lc-pipeline{list-style:none;margin:0;padding:0;display:flex}.lc-pipeline__step{position:relative;display:flex;min-width:0}.lc-pipeline__hit{display:flex;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-align:inherit;cursor:pointer;width:100%}.lc-pipeline__hit:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px;border-radius:var(--border-radius-sm)}.lc-pipeline__marker{flex-shrink:0}.lc-pipeline__node{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:var(--lc-pipeline-node-size);height:var(--lc-pipeline-node-size);border-radius:var(--border-radius-full);background-color:var(--lc-pipeline-node-bg);border:var(--lc-pipeline-line-weight) solid var(--lc-pipeline-node-border);color:var(--lc-pipeline-node-fg);box-sizing:border-box}.lc-pipeline__label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}.lc-pipeline__caption{display:block;font-size:var(--font-size-xs);color:var(--color-text-tertiary);line-height:1.35}.lc-pipeline--horizontal{align-items:flex-start;gap:var(--lc-pipeline-gap)}.lc-pipeline--horizontal .lc-pipeline__step{flex:1 1 0;flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__hit{flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__connector{position:absolute;top:calc(var(--lc-pipeline-node-size) / 2);left:calc(var(--lc-pipeline-node-size) / 2);width:calc(100% + var(--lc-pipeline-gap));height:var(--lc-pipeline-line-weight);transform:translateY(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--horizontal .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--horizontal .lc-pipeline__text{margin-top:var(--spacing-2);padding-right:var(--spacing-3)}.lc-pipeline--vertical{flex-direction:column;gap:0}.lc-pipeline--vertical .lc-pipeline__step{flex-direction:row;align-items:flex-start;gap:var(--spacing-3);padding-bottom:var(--spacing-4)}.lc-pipeline--vertical .lc-pipeline__hit{flex-direction:row;align-items:flex-start;gap:var(--spacing-3)}.lc-pipeline--vertical .lc-pipeline__connector{position:absolute;left:calc(var(--lc-pipeline-node-size) / 2);top:var(--lc-pipeline-node-size);width:var(--lc-pipeline-line-weight);height:calc(100% - var(--lc-pipeline-node-size));transform:translate(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--vertical .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--vertical .lc-pipeline__text{padding-top:calc((var(--lc-pipeline-node-size) - 1.25rem) / 2)}.lc-pipeline__step--complete .lc-pipeline__node{background-color:var(--color-primary-500);border-color:var(--color-primary-500);color:var(--color-
|
|
126
|
+
styles: [":host{display:block}.lc-pipeline{--lc-pipeline-node-size: 1.75rem;--lc-pipeline-line-weight: 2px;--lc-pipeline-gap: var(--spacing-2);--lc-pipeline-line: var(--color-border-strong);--lc-pipeline-line-done: var(--color-primary-500);--lc-pipeline-node-bg: var(--color-surface);--lc-pipeline-node-fg: var(--color-text-tertiary);--lc-pipeline-node-border: var(--lc-pipeline-line)}.lc-pipeline--size-sm{--lc-pipeline-node-size: 1.25rem;--lc-pipeline-line-weight: 1px;--lc-pipeline-gap: var(--spacing-1)}.lc-pipeline--size-sm .lc-pipeline__label{font-size:var(--font-size-xs)}.lc-pipeline--size-sm .lc-pipeline__caption{font-size:var(--font-size-2xs, .6875rem)}.lc-pipeline--size-sm .lc-pipeline__node lc-icon{transform:scale(.85)}.lc-pipeline{list-style:none;margin:0;padding:0;display:flex}.lc-pipeline__step{position:relative;display:flex;min-width:0}.lc-pipeline__hit{display:flex;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-align:inherit;cursor:pointer;width:100%}.lc-pipeline__hit:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px;border-radius:var(--border-radius-sm)}.lc-pipeline__marker{flex-shrink:0}.lc-pipeline__node{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:var(--lc-pipeline-node-size);height:var(--lc-pipeline-node-size);border-radius:var(--border-radius-full);background-color:var(--lc-pipeline-node-bg);border:var(--lc-pipeline-line-weight) solid var(--lc-pipeline-node-border);color:var(--lc-pipeline-node-fg);box-sizing:border-box}.lc-pipeline__label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}.lc-pipeline__caption{display:block;font-size:var(--font-size-xs);color:var(--color-text-tertiary);line-height:1.35}.lc-pipeline--horizontal{align-items:flex-start;gap:var(--lc-pipeline-gap)}.lc-pipeline--horizontal .lc-pipeline__step{flex:1 1 0;flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__hit{flex-direction:column;align-items:flex-start}.lc-pipeline--horizontal .lc-pipeline__connector{position:absolute;top:calc(var(--lc-pipeline-node-size) / 2);left:calc(var(--lc-pipeline-node-size) / 2);width:calc(100% + var(--lc-pipeline-gap));height:var(--lc-pipeline-line-weight);transform:translateY(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--horizontal .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--horizontal .lc-pipeline__text{margin-top:var(--spacing-2);padding-right:var(--spacing-3)}.lc-pipeline--vertical{flex-direction:column;gap:0}.lc-pipeline--vertical .lc-pipeline__step{flex-direction:row;align-items:flex-start;gap:var(--spacing-3);padding-bottom:var(--spacing-4)}.lc-pipeline--vertical .lc-pipeline__hit{flex-direction:row;align-items:flex-start;gap:var(--spacing-3)}.lc-pipeline--vertical .lc-pipeline__connector{position:absolute;left:calc(var(--lc-pipeline-node-size) / 2);top:var(--lc-pipeline-node-size);width:var(--lc-pipeline-line-weight);height:calc(100% - var(--lc-pipeline-node-size));transform:translate(-50%);background-color:var(--lc-pipeline-line)}.lc-pipeline--vertical .lc-pipeline__step--last .lc-pipeline__connector{display:none}.lc-pipeline--vertical .lc-pipeline__text{padding-top:calc((var(--lc-pipeline-node-size) - 1.25rem) / 2)}.lc-pipeline__step--complete .lc-pipeline__node{background-color:var(--color-primary-500);border-color:var(--color-primary-500);color:var(--color-surface)}.lc-pipeline__step--complete .lc-pipeline__connector{background-color:var(--lc-pipeline-line-done)}.lc-pipeline__step--current .lc-pipeline__node{border-color:var(--color-primary-500);color:var(--color-primary-500);box-shadow:var(--shadow-brand-glow)}.lc-pipeline__step--current .lc-pipeline__connector{background-color:var(--lc-pipeline-line-done)}.lc-pipeline__step--warning .lc-pipeline__node{border-color:var(--color-on-warning-subtle);color:var(--color-on-warning-subtle)}.lc-pipeline__step--error .lc-pipeline__node{border-color:var(--color-on-error-subtle);color:var(--color-on-error-subtle)}.lc-pipeline__step--pending .lc-pipeline__node{border-style:dashed}.lc-pipeline__step--pending .lc-pipeline__label{color:var(--color-text-secondary)}\n"]
|
|
127
127
|
}]
|
|
128
128
|
}],
|
|
129
129
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-pipeline.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pipeline/pipeline.component.ts","../../../../libs/angular-ui-kit/src/lib/pipeline/pipeline.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_ALERT_TRIANGLE, ICON_CHECK, ICON_X } from '@lifecorner/angular-ui-kit/icons';\n\n/**\n * Status of a single pipeline node.\n * - complete: finished (teal check)\n * - current: in progress / active (teal, subtle pulse)\n * - pending: not started yet (muted, hollow)\n * - warning: needs attention (amber)\n * - error: failed (red)\n */\nexport type PipelineStatus =\n | 'complete'\n | 'current'\n | 'pending'\n | 'warning'\n | 'error';\n\nexport interface PipelineStep {\n /** Node label. */\n label: string;\n /** Optional secondary line under the label. */\n caption?: string;\n /** Status that drives the node color and default icon. */\n status: PipelineStatus;\n /** Optional icon (Tabler name) overriding the status default. */\n icon?: string;\n /** Optional opaque payload echoed back in `stepClick`. */\n id?: string;\n}\n\nexport type PipelineOrientation = 'horizontal' | 'vertical';\nexport type PipelineSize = 'sm' | 'md';\n\nconst STATUS_ICON: Record<PipelineStatus, string> = {\n complete: 'check',\n current: 'point',\n pending: '',\n warning: 'alert-triangle',\n error: 'x',\n};\n\n/**\n * Pipeline — a status timeline of connected process nodes.\n *\n * Unlike {@link StepperComponent} (a navigation stepper whose states derive\n * from the active index), each pipeline node carries its own explicit status,\n * so it can show completed, current, pending, warning and error nodes in the\n * same chain — with an optional caption under each label.\n *\n * @example\n * ```html\n * <lc-pipeline\n * [steps]=\"[\n * { label: 'Connect', caption: 'Token valid', status: 'complete' },\n * { label: 'Checkout', caption: '2 hours ago', status: 'complete' },\n * { label: 'Analyze', caption: 'Running…', status: 'current' },\n * { label: 'Graph', caption: 'Out of date', status: 'warning' }\n * ]\" />\n * ```\n */\n@Component({\n selector: 'lc-pipeline',\n standalone: true,\n imports: [IconComponent, NgTemplateOutlet],\n viewProviders: [ɵprovideKitIcons([ICON_CHECK, ICON_ALERT_TRIANGLE, ICON_X])],\n templateUrl: './pipeline.component.html',\n styleUrls: ['./pipeline.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PipelineComponent {\n /** Ordered pipeline nodes. */\n readonly steps = input.required<readonly PipelineStep[]>();\n\n /** Layout direction. @default 'horizontal' */\n readonly orientation = input<PipelineOrientation>('horizontal');\n\n /**\n * Node/connector scale.\n * - md: default nodes (1.75rem)\n * - sm: compact spine — smaller nodes, thinner connectors, tighter grid\n * @default 'md'\n */\n readonly size = input<PipelineSize>('md');\n\n /** Whether nodes are clickable (emits `stepClick`). @default false */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a node is activated (only when `clickable`). */\n readonly stepClick = output<PipelineStep>();\n\n protected readonly hostClass = computed(\n () =>\n `lc-pipeline lc-pipeline--${this.orientation()} lc-pipeline--size-${this.size()}`,\n );\n\n protected iconFor(step: PipelineStep): string {\n return step.icon ?? STATUS_ICON[step.status];\n }\n\n protected stepClass(step: PipelineStep): string {\n return `lc-pipeline__step lc-pipeline__step--${step.status}`;\n }\n\n protected onActivate(step: PipelineStep): void {\n if (this.clickable()) {\n this.stepClick.emit(step);\n }\n }\n}\n","<ol [class]=\"hostClass()\">\n @for (step of steps(); track $index; let last = $last) {\n <li [class]=\"stepClass(step)\" [class.lc-pipeline__step--last]=\"last\">\n @if (clickable()) {\n <button type=\"button\" class=\"lc-pipeline__hit\" (click)=\"onActivate(step)\">\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n </button>\n } @else {\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n }\n </li>\n }\n</ol>\n\n<ng-template #body let-step>\n <span class=\"lc-pipeline__marker\">\n <span class=\"lc-pipeline__connector\" aria-hidden=\"true\"></span>\n <span class=\"lc-pipeline__node\">\n @if (iconFor(step)) {\n <lc-icon [name]=\"iconFor(step)\" size=\"xs\" [decorative]=\"true\" />\n }\n </span>\n </span>\n <span class=\"lc-pipeline__text\">\n <span class=\"lc-pipeline__label\">{{ step.label }}</span>\n @if (step.caption) {\n <span class=\"lc-pipeline__caption\">{{ step.caption }}</span>\n }\n </span>\n</ng-template>\n"],"mappings":";;;;;AA0CA,MAAM,cAA8C;CAClD,UAAU;CACV,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;AACR;AA8BD,IAAa,oBAAb,MAAa,kBAAiB;CAEnB,QAAQ,MAAM,SAAmC,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGjD,cAAc,MAA2B,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAQtD,OAAO,MAAoB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAGhC,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAER,YAAY,eAE3B,4BAA4B,KAAK,YAAW,EAAE,qBAAsB,KAAK,KAAI,KAChF,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAES,QAAQ,MAAkB;EAClC,OAAO,KAAK,QAAQ,YAAY,KAAK;CACvC;CAEU,UAAU,MAAkB;EACpC,OAAO,wCAAwC,KAAK;CACtD;CAEU,WAAW,MAAkB;EACrC,IAAI,KAAK,UAAS,GAChB,KAAK,UAAU,KAAK,IAAI;CAE5B;CAtCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAiB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;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;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UC9E9B;EA8BA,QAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-pipeline.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pipeline/pipeline.component.ts","../../../../libs/angular-ui-kit/src/lib/pipeline/pipeline.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_ALERT_TRIANGLE, ICON_CHECK, ICON_X } from '@lifecorner/angular-ui-kit/icons';\n\n/**\n * Status of a single pipeline node.\n * - complete: finished (teal check)\n * - current: in progress / active (teal, subtle pulse)\n * - pending: not started yet (muted, hollow)\n * - warning: needs attention (amber)\n * - error: failed (red)\n */\nexport type PipelineStatus =\n | 'complete'\n | 'current'\n | 'pending'\n | 'warning'\n | 'error';\n\nexport interface PipelineStep {\n /** Node label. */\n label: string;\n /** Optional secondary line under the label. */\n caption?: string;\n /** Status that drives the node color and default icon. */\n status: PipelineStatus;\n /** Optional icon (Tabler name) overriding the status default. */\n icon?: string;\n /** Optional opaque payload echoed back in `stepClick`. */\n id?: string;\n}\n\nexport type PipelineOrientation = 'horizontal' | 'vertical';\nexport type PipelineSize = 'sm' | 'md';\n\nconst STATUS_ICON: Record<PipelineStatus, string> = {\n complete: 'check',\n current: 'point',\n pending: '',\n warning: 'alert-triangle',\n error: 'x',\n};\n\n/**\n * Pipeline — a status timeline of connected process nodes.\n *\n * Unlike {@link StepperComponent} (a navigation stepper whose states derive\n * from the active index), each pipeline node carries its own explicit status,\n * so it can show completed, current, pending, warning and error nodes in the\n * same chain — with an optional caption under each label.\n *\n * @example\n * ```html\n * <lc-pipeline\n * [steps]=\"[\n * { label: 'Connect', caption: 'Token valid', status: 'complete' },\n * { label: 'Checkout', caption: '2 hours ago', status: 'complete' },\n * { label: 'Analyze', caption: 'Running…', status: 'current' },\n * { label: 'Graph', caption: 'Out of date', status: 'warning' }\n * ]\" />\n * ```\n */\n@Component({\n selector: 'lc-pipeline',\n standalone: true,\n imports: [IconComponent, NgTemplateOutlet],\n viewProviders: [ɵprovideKitIcons([ICON_CHECK, ICON_ALERT_TRIANGLE, ICON_X])],\n templateUrl: './pipeline.component.html',\n styleUrls: ['./pipeline.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PipelineComponent {\n /** Ordered pipeline nodes. */\n readonly steps = input.required<readonly PipelineStep[]>();\n\n /** Layout direction. @default 'horizontal' */\n readonly orientation = input<PipelineOrientation>('horizontal');\n\n /**\n * Node/connector scale.\n * - md: default nodes (1.75rem)\n * - sm: compact spine — smaller nodes, thinner connectors, tighter grid\n * @default 'md'\n */\n readonly size = input<PipelineSize>('md');\n\n /** Whether nodes are clickable (emits `stepClick`). @default false */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a node is activated (only when `clickable`). */\n readonly stepClick = output<PipelineStep>();\n\n protected readonly hostClass = computed(\n () =>\n `lc-pipeline lc-pipeline--${this.orientation()} lc-pipeline--size-${this.size()}`,\n );\n\n protected iconFor(step: PipelineStep): string {\n return step.icon ?? STATUS_ICON[step.status];\n }\n\n protected stepClass(step: PipelineStep): string {\n return `lc-pipeline__step lc-pipeline__step--${step.status}`;\n }\n\n protected onActivate(step: PipelineStep): void {\n if (this.clickable()) {\n this.stepClick.emit(step);\n }\n }\n}\n","<ol [class]=\"hostClass()\">\n @for (step of steps(); track $index; let last = $last) {\n <li [class]=\"stepClass(step)\" [class.lc-pipeline__step--last]=\"last\">\n @if (clickable()) {\n <button type=\"button\" class=\"lc-pipeline__hit\" (click)=\"onActivate(step)\">\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n </button>\n } @else {\n <ng-container [ngTemplateOutlet]=\"body\" [ngTemplateOutletContext]=\"{ $implicit: step }\" />\n }\n </li>\n }\n</ol>\n\n<ng-template #body let-step>\n <span class=\"lc-pipeline__marker\">\n <span class=\"lc-pipeline__connector\" aria-hidden=\"true\"></span>\n <span class=\"lc-pipeline__node\">\n @if (iconFor(step)) {\n <lc-icon [name]=\"iconFor(step)\" size=\"xs\" [decorative]=\"true\" />\n }\n </span>\n </span>\n <span class=\"lc-pipeline__text\">\n <span class=\"lc-pipeline__label\">{{ step.label }}</span>\n @if (step.caption) {\n <span class=\"lc-pipeline__caption\">{{ step.caption }}</span>\n }\n </span>\n</ng-template>\n"],"mappings":";;;;;AA0CA,MAAM,cAA8C;CAClD,UAAU;CACV,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;AACR;AA8BD,IAAa,oBAAb,MAAa,kBAAiB;CAEnB,QAAQ,MAAM,SAAmC,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGjD,cAAc,MAA2B,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAQtD,OAAO,MAAoB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAGhC,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAER,YAAY,eAE3B,4BAA4B,KAAK,YAAW,EAAE,qBAAsB,KAAK,KAAI,KAChF,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAES,QAAQ,MAAkB;EAClC,OAAO,KAAK,QAAQ,YAAY,KAAK;CACvC;CAEU,UAAU,MAAkB;EACpC,OAAO,wCAAwC,KAAK;CACtD;CAEU,WAAW,MAAkB;EACrC,IAAI,KAAK,UAAS,GAChB,KAAK,UAAU,KAAK,IAAI;CAE5B;CAtCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAiB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;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;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UC9E9B;EA8BA,QAAA,CAAA,spIAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD0CY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAAE;GAAgB,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eAC1B,CAAC,iBAAiB;GAAC;GAAY;GAAqB;EAAM,CAAC,CAAC;EAAC,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAKjE,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAiB,YAAA,CAAA;EAT7B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAa,YACX;GAAI,SACP,CAAC,eAAe,gBAAgB;GAAC,eAC3B,CAAC,iBAAiB;IAAC;IAAY;IAAqB;GAAM,CAAC,CAAC;GAAC,iBAG3D,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,spIAAA;EAAA,CAAA"}
|