@lifecorner/angular-ui-kit 3.5.0 → 3.6.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/README.md +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-accordion.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-accordion.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +16 -1
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs +16 -1
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +14 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-code-block.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs +14 -1
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-drawer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-email-input.mjs +4 -0
- package/fesm2022/lifecorner-angular-ui-kit-email-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-footer.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-footer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +14 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +18 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-icon.mjs +186 -275
- package/fesm2022/lifecorner-angular-ui-kit-icon.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-icons.mjs +803 -0
- package/fesm2022/lifecorner-angular-ui-kit-icons.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs +297 -0
- package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-internal-navigation.mjs +38 -0
- package/fesm2022/lifecorner-angular-ui-kit-internal-navigation.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-modal.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-modal.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +12 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +14 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-password-input.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-password-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-pipeline.mjs +12 -1
- package/fesm2022/lifecorner-angular-ui-kit-pipeline.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rating.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-rating.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-sidenav-placeholder.mjs +262 -0
- package/fesm2022/lifecorner-angular-ui-kit-sidenav-placeholder.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +18 -40
- package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +4 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs +16 -1
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs +18 -1
- package/fesm2022/lifecorner-angular-ui-kit-tree-view.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit.mjs +2 -0
- package/package.json +17 -1
- package/types/lifecorner-angular-ui-kit-icon.d.ts +125 -5
- package/types/lifecorner-angular-ui-kit-icons.d.ts +140 -0
- package/types/lifecorner-angular-ui-kit-internal-icon-set.d.ts +31 -0
- package/types/lifecorner-angular-ui-kit-internal-navigation.d.ts +28 -0
- package/types/lifecorner-angular-ui-kit-sidenav-placeholder.d.ts +163 -0
- package/types/lifecorner-angular-ui-kit-sidenav.d.ts +4 -2
- package/types/lifecorner-angular-ui-kit.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-document-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\nimport { SpinnerComponent } from '@lifecorner/angular-ui-kit/spinner';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { MarkdownComponent } from '@lifecorner/angular-ui-kit/markdown';\nimport { isSafeResourceUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport type DocumentType = 'pdf' | 'markdown' | 'image' | 'text' | 'code' | 'auto';\n\nconst EXTENSION_TYPE_MAP: Record<string, DocumentType> = {\n pdf: 'pdf',\n md: 'markdown',\n markdown: 'markdown',\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n svg: 'image',\n bmp: 'image',\n ts: 'code',\n js: 'code',\n tsx: 'code',\n jsx: 'code',\n py: 'code',\n java: 'code',\n html: 'code',\n css: 'code',\n scss: 'code',\n json: 'code',\n sh: 'code',\n bash: 'code',\n txt: 'text',\n log: 'text',\n csv: 'text',\n};\n\nconst EXTENSION_LANGUAGE_MAP: Record<string, CodeBlockLanguage> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n java: 'java',\n html: 'html',\n css: 'css',\n scss: 'scss',\n json: 'json',\n sh: 'bash',\n bash: 'bash',\n};\n\nconst TYPE_ICONS: Record<string, string> = {\n pdf: 'document-text',\n markdown: 'document-text',\n image: 'photo',\n text: 'document',\n code: 'code-bracket',\n unknown: 'document',\n};\n\n/**\n * Document viewer component for previewing various file types.\n *\n * Features:\n * - Auto-detects file type from URL extension or explicit type input\n * - PDF rendering via browser-native iframe viewer\n * - Markdown rendered through `lc-markdown` (escaped and sanitised — a fetched\n * `.md` file cannot inject markup)\n * - Image display with zoom controls (25% – 500%)\n * - Code display using the built-in code block component\n * - Plain text display\n * - Toolbar with filename, type badge, zoom, download, and fullscreen\n * - Loading and error states\n * - Dark/light theme support\n *\n * `src` is only ever embedded (iframe) or downloaded when it is a http(s),\n * blob: or relative URL — or a `data:application/pdf` — so a `javascript:` URL\n * handed in as a document can neither run in the frame nor be \"downloaded\".\n *\n * @example\n * ```html\n * <lc-document-viewer src=\"https://example.com/report.pdf\" />\n * <lc-document-viewer [content]=\"markdownString\" type=\"markdown\" filename=\"README.md\" />\n * <lc-document-viewer src=\"/assets/diagram.png\" />\n * ```\n */\n@Component({\n selector: 'lc-document-viewer',\n standalone: true,\n imports: [IconComponent, SpinnerComponent, CodeBlockComponent, MarkdownComponent],\n templateUrl: './document-viewer.component.html',\n styleUrls: ['./document-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': '\"doc-viewer\"',\n '[style.height]': 'height()',\n },\n})\nexport class DocumentViewerComponent implements OnDestroy {\n /** URL of the document to display (for PDF, images, or remote files) */\n readonly src = input<string>('');\n\n /** Direct content string (for markdown, text, or code — takes precedence over src for content-based types) */\n readonly content = input<string>('');\n\n /** File type — set to 'auto' to detect from URL extension */\n readonly type = input<DocumentType>('auto');\n\n /** Display name shown in the toolbar */\n readonly filename = input<string>('');\n\n /** Code language for syntax highlighting (when type is 'code') */\n readonly language = input<CodeBlockLanguage>('text');\n\n /** Whether to display the toolbar */\n readonly showToolbar = input(true);\n\n /** Whether to show the download button in the toolbar */\n readonly showDownload = input(true);\n\n /** Height of the viewer container */\n readonly height = input('500px');\n\n /** Accessible name of the zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the toolbar download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the fullscreen button. */\n readonly fullscreenAriaLabel = input('Fullscreen');\n\n /** Accessible name of the fullscreen button while in fullscreen. */\n readonly exitFullscreenAriaLabel = input('Exit fullscreen');\n\n /** Shown while the document loads. */\n readonly loadingLabel = input('Loading document…');\n\n /** Title shown for file types without a preview. */\n readonly previewUnavailableTitle = input('Preview not available');\n\n /** Message shown for file types without a preview. */\n readonly previewUnavailableMessage = input('This file type cannot be previewed.');\n\n /** Label of the download button shown instead of a preview. */\n readonly downloadFileLabel = input('Download file');\n\n /** Toolbar name when neither `filename` nor a file name in `src` is available. */\n readonly untitledLabel = input('Document');\n\n /** Type badge for Markdown documents. */\n readonly markdownTypeLabel = input('Markdown');\n\n /** Type badge for images. */\n readonly imageTypeLabel = input('Image');\n\n /** Type badge for plain text. */\n readonly textTypeLabel = input('Text');\n\n /** Type badge for other files. */\n readonly fileTypeLabel = input('File');\n\n /** Prefix of the error shown when fetching the document fails. */\n readonly loadErrorLabel = input('Failed to load document:');\n\n /** Error shown when `src` is not an allowed URL (e.g. `javascript:`). */\n readonly blockedUrlMessage = input('This document URL cannot be displayed');\n\n // ── Internal state ───────────────────────────────────────────────────────\n\n protected loading = signal(true);\n protected error = signal<string | null>(null);\n protected fetchedContent = signal<string>('');\n protected zoom = signal(100);\n protected isFullscreen = signal(false);\n\n private readonly sanitizer = inject(DomSanitizer);\n private readonly elementRef = inject(ElementRef);\n private fullscreenHandler: (() => void) | null = null;\n\n // ── Computed values ──────────────────────────────────────────────────────\n\n /** Resolved document type from input or URL extension */\n protected resolvedType = computed<DocumentType | 'unknown'>(() => {\n const t = this.type();\n if (t !== 'auto') return t;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_TYPE_MAP[ext] ? EXTENSION_TYPE_MAP[ext] : 'unknown';\n });\n\n /** Display name for the toolbar */\n protected displayName = computed(() => {\n if (this.filename()) return this.filename();\n const src = this.src();\n if (!src) return this.untitledLabel();\n try {\n const url = new URL(src, 'http://localhost');\n const segments = url.pathname.split('/');\n return segments[segments.length - 1] || this.untitledLabel();\n } catch {\n return src.split('/').pop() || this.untitledLabel();\n }\n });\n\n /** Icon name for the document type */\n protected typeIcon = computed(() => TYPE_ICONS[this.resolvedType()] || TYPE_ICONS['unknown']);\n\n /** Type label for the toolbar badge */\n protected typeLabel = computed(() => {\n const t = this.resolvedType();\n switch (t) {\n case 'pdf': return 'PDF';\n case 'markdown': return this.markdownTypeLabel();\n case 'image': return this.imageTypeLabel();\n case 'text': return this.textTypeLabel();\n case 'code': return this.resolvedLanguage().toUpperCase();\n default: return this.fileTypeLabel();\n }\n });\n\n /** Resolved code language */\n protected resolvedLanguage = computed<CodeBlockLanguage>(() => {\n const lang = this.language();\n if (lang !== 'text') return lang;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_LANGUAGE_MAP[ext] ? EXTENSION_LANGUAGE_MAP[ext] : 'text';\n });\n\n /** Whether `src` may be embedded in a frame or downloaded at all. */\n protected srcEmbeddable = computed(() => isSafeResourceUrl(this.src()));\n\n /**\n * `src` as the iframe accepts it. Bypassing the resource-URL check is what\n * lets a PDF URL into an iframe at all; it is only done for URLs that passed\n * `isSafeResourceUrl` — anything else resolves to an empty frame.\n */\n protected safeSrc = computed<SafeResourceUrl>(() => {\n const src = this.src();\n return this.sanitizer.bypassSecurityTrustResourceUrl(this.srcEmbeddable() ? src : 'about:blank');\n });\n\n /** Effective content: direct content input or fetched content */\n protected effectiveContent = computed(() => {\n return this.content() || this.fetchedContent();\n });\n\n /** Image transform style for zoom */\n protected imageTransform = computed(() => {\n const z = this.zoom() / 100;\n return `scale(${z})`;\n });\n\n /** Whether zoom controls should be shown */\n protected showZoom = computed(() => {\n const t = this.resolvedType();\n return t === 'image';\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n constructor() {\n // Load whenever the document changes — not just once. A new `src` starts a\n // fresh load (and cancels the one still in flight, so a slow old response\n // can't overwrite the new document), resets any earlier error, and re-enters\n // the loading state; a `content` string short-circuits all of that.\n effect((onCleanup) => {\n const type = this.resolvedType();\n const src = this.src();\n const content = this.content();\n const controller = typeof AbortController === 'function' ? new AbortController() : null;\n onCleanup(() => controller?.abort());\n untracked(() => this.load(type, src, content, controller));\n });\n }\n\n private load(\n type: DocumentType | 'unknown',\n src: string,\n content: string,\n controller: AbortController | null,\n ): void {\n this.error.set(null);\n this.fetchedContent.set('');\n\n if (content) {\n this.loading.set(false);\n return;\n }\n\n if (!src) {\n this.loading.set(false);\n this.error.set('No source or content provided');\n return;\n }\n\n // PDF and images are embedded directly — no fetch needed. They do have to be\n // embeddable, though: a `javascript:` (or other exotic) URL is refused up\n // front instead of being handed to an iframe.\n if (type === 'pdf' || type === 'image') {\n if (!this.srcEmbeddable()) {\n this.loading.set(false);\n this.error.set(this.blockedUrlMessage());\n return;\n }\n // Not \"loading\": the frame/img element only exists once loading is off,\n // and it is its own load event that would end the state — a deadlock.\n this.loading.set(false);\n return;\n }\n\n // Fetch text-based content\n if (type === 'markdown' || type === 'text' || type === 'code') {\n this.loading.set(true);\n this.fetchContent(src, controller);\n } else {\n this.loading.set(false);\n }\n }\n\n ngOnDestroy(): void {\n if (this.fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n // ── Actions ──────────────────────────────────────────────────────────────\n\n protected zoomIn(): void {\n this.zoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.zoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.zoom.set(100);\n }\n\n protected download(): void {\n const src = this.src();\n // A download is a click the user never sees the target of — only URLs that\n // could be embedded are worth clicking.\n if (!src || !this.srcEmbeddable()) return;\n const a = document.createElement('a');\n a.href = src;\n a.download = this.displayName();\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected toggleFullscreen(): void {\n const el = this.elementRef.nativeElement as HTMLElement;\n if (!document.fullscreenElement) {\n el.requestFullscreen?.();\n this.isFullscreen.set(true);\n } else {\n document.exitFullscreen?.();\n this.isFullscreen.set(false);\n }\n\n if (!this.fullscreenHandler) {\n this.fullscreenHandler = () => {\n this.isFullscreen.set(!!document.fullscreenElement);\n };\n document.addEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n protected onIframeLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageError(): void {\n this.loading.set(false);\n this.error.set('Failed to load image');\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private extractExtension(url: string): string | null {\n if (!url) return null;\n try {\n const pathname = new URL(url, 'http://localhost').pathname;\n const parts = pathname.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase() : null;\n } catch {\n const parts = url.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase().split('?')[0] : null;\n }\n }\n\n private async fetchContent(url: string, controller: AbortController | null): Promise<void> {\n if (typeof fetch !== 'function') {\n this.loading.set(false);\n this.error.set('Failed to load document: fetch is not available');\n return;\n }\n try {\n const response = await fetch(url, controller ? { signal: controller.signal } : undefined);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n const text = await response.text();\n // A response for a document that has since been replaced is dropped: the\n // effect that replaced it aborted this controller and started its own load.\n if (controller?.signal.aborted) return;\n this.fetchedContent.set(text);\n this.loading.set(false);\n } catch (err) {\n if (controller?.signal.aborted) return;\n this.loading.set(false);\n this.error.set(`${this.loadErrorLabel()} ${err instanceof Error ? err.message : 'Unknown error'}`);\n }\n }\n}\n","<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-document-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT, ICON_PLUS } from '@lifecorner/angular-ui-kit/icons';\nimport { SpinnerComponent } from '@lifecorner/angular-ui-kit/spinner';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { MarkdownComponent } from '@lifecorner/angular-ui-kit/markdown';\nimport { isSafeResourceUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport type DocumentType = 'pdf' | 'markdown' | 'image' | 'text' | 'code' | 'auto';\n\nconst EXTENSION_TYPE_MAP: Record<string, DocumentType> = {\n pdf: 'pdf',\n md: 'markdown',\n markdown: 'markdown',\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n svg: 'image',\n bmp: 'image',\n ts: 'code',\n js: 'code',\n tsx: 'code',\n jsx: 'code',\n py: 'code',\n java: 'code',\n html: 'code',\n css: 'code',\n scss: 'code',\n json: 'code',\n sh: 'code',\n bash: 'code',\n txt: 'text',\n log: 'text',\n csv: 'text',\n};\n\nconst EXTENSION_LANGUAGE_MAP: Record<string, CodeBlockLanguage> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n java: 'java',\n html: 'html',\n css: 'css',\n scss: 'scss',\n json: 'json',\n sh: 'bash',\n bash: 'bash',\n};\n\nconst TYPE_ICONS: Record<string, string> = {\n pdf: 'document-text',\n markdown: 'document-text',\n image: 'photo',\n text: 'document',\n code: 'code-bracket',\n unknown: 'document',\n};\n\n/**\n * Document viewer component for previewing various file types.\n *\n * Features:\n * - Auto-detects file type from URL extension or explicit type input\n * - PDF rendering via browser-native iframe viewer\n * - Markdown rendered through `lc-markdown` (escaped and sanitised — a fetched\n * `.md` file cannot inject markup)\n * - Image display with zoom controls (25% – 500%)\n * - Code display using the built-in code block component\n * - Plain text display\n * - Toolbar with filename, type badge, zoom, download, and fullscreen\n * - Loading and error states\n * - Dark/light theme support\n *\n * `src` is only ever embedded (iframe) or downloaded when it is a http(s),\n * blob: or relative URL — or a `data:application/pdf` — so a `javascript:` URL\n * handed in as a document can neither run in the frame nor be \"downloaded\".\n *\n * @example\n * ```html\n * <lc-document-viewer src=\"https://example.com/report.pdf\" />\n * <lc-document-viewer [content]=\"markdownString\" type=\"markdown\" filename=\"README.md\" />\n * <lc-document-viewer src=\"/assets/diagram.png\" />\n * ```\n */\n@Component({\n selector: 'lc-document-viewer',\n standalone: true,\n imports: [IconComponent, SpinnerComponent, CodeBlockComponent, MarkdownComponent],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS, ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT])],\n templateUrl: './document-viewer.component.html',\n styleUrls: ['./document-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': '\"doc-viewer\"',\n '[style.height]': 'height()',\n },\n})\nexport class DocumentViewerComponent implements OnDestroy {\n /** URL of the document to display (for PDF, images, or remote files) */\n readonly src = input<string>('');\n\n /** Direct content string (for markdown, text, or code — takes precedence over src for content-based types) */\n readonly content = input<string>('');\n\n /** File type — set to 'auto' to detect from URL extension */\n readonly type = input<DocumentType>('auto');\n\n /** Display name shown in the toolbar */\n readonly filename = input<string>('');\n\n /** Code language for syntax highlighting (when type is 'code') */\n readonly language = input<CodeBlockLanguage>('text');\n\n /** Whether to display the toolbar */\n readonly showToolbar = input(true);\n\n /** Whether to show the download button in the toolbar */\n readonly showDownload = input(true);\n\n /** Height of the viewer container */\n readonly height = input('500px');\n\n /** Accessible name of the zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the toolbar download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the fullscreen button. */\n readonly fullscreenAriaLabel = input('Fullscreen');\n\n /** Accessible name of the fullscreen button while in fullscreen. */\n readonly exitFullscreenAriaLabel = input('Exit fullscreen');\n\n /** Shown while the document loads. */\n readonly loadingLabel = input('Loading document…');\n\n /** Title shown for file types without a preview. */\n readonly previewUnavailableTitle = input('Preview not available');\n\n /** Message shown for file types without a preview. */\n readonly previewUnavailableMessage = input('This file type cannot be previewed.');\n\n /** Label of the download button shown instead of a preview. */\n readonly downloadFileLabel = input('Download file');\n\n /** Toolbar name when neither `filename` nor a file name in `src` is available. */\n readonly untitledLabel = input('Document');\n\n /** Type badge for Markdown documents. */\n readonly markdownTypeLabel = input('Markdown');\n\n /** Type badge for images. */\n readonly imageTypeLabel = input('Image');\n\n /** Type badge for plain text. */\n readonly textTypeLabel = input('Text');\n\n /** Type badge for other files. */\n readonly fileTypeLabel = input('File');\n\n /** Prefix of the error shown when fetching the document fails. */\n readonly loadErrorLabel = input('Failed to load document:');\n\n /** Error shown when `src` is not an allowed URL (e.g. `javascript:`). */\n readonly blockedUrlMessage = input('This document URL cannot be displayed');\n\n // ── Internal state ───────────────────────────────────────────────────────\n\n protected loading = signal(true);\n protected error = signal<string | null>(null);\n protected fetchedContent = signal<string>('');\n protected zoom = signal(100);\n protected isFullscreen = signal(false);\n\n private readonly sanitizer = inject(DomSanitizer);\n private readonly elementRef = inject(ElementRef);\n private fullscreenHandler: (() => void) | null = null;\n\n // ── Computed values ──────────────────────────────────────────────────────\n\n /** Resolved document type from input or URL extension */\n protected resolvedType = computed<DocumentType | 'unknown'>(() => {\n const t = this.type();\n if (t !== 'auto') return t;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_TYPE_MAP[ext] ? EXTENSION_TYPE_MAP[ext] : 'unknown';\n });\n\n /** Display name for the toolbar */\n protected displayName = computed(() => {\n if (this.filename()) return this.filename();\n const src = this.src();\n if (!src) return this.untitledLabel();\n try {\n const url = new URL(src, 'http://localhost');\n const segments = url.pathname.split('/');\n return segments[segments.length - 1] || this.untitledLabel();\n } catch {\n return src.split('/').pop() || this.untitledLabel();\n }\n });\n\n /** Icon name for the document type */\n protected typeIcon = computed(() => TYPE_ICONS[this.resolvedType()] || TYPE_ICONS['unknown']);\n\n /** Type label for the toolbar badge */\n protected typeLabel = computed(() => {\n const t = this.resolvedType();\n switch (t) {\n case 'pdf': return 'PDF';\n case 'markdown': return this.markdownTypeLabel();\n case 'image': return this.imageTypeLabel();\n case 'text': return this.textTypeLabel();\n case 'code': return this.resolvedLanguage().toUpperCase();\n default: return this.fileTypeLabel();\n }\n });\n\n /** Resolved code language */\n protected resolvedLanguage = computed<CodeBlockLanguage>(() => {\n const lang = this.language();\n if (lang !== 'text') return lang;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_LANGUAGE_MAP[ext] ? EXTENSION_LANGUAGE_MAP[ext] : 'text';\n });\n\n /** Whether `src` may be embedded in a frame or downloaded at all. */\n protected srcEmbeddable = computed(() => isSafeResourceUrl(this.src()));\n\n /**\n * `src` as the iframe accepts it. Bypassing the resource-URL check is what\n * lets a PDF URL into an iframe at all; it is only done for URLs that passed\n * `isSafeResourceUrl` — anything else resolves to an empty frame.\n */\n protected safeSrc = computed<SafeResourceUrl>(() => {\n const src = this.src();\n return this.sanitizer.bypassSecurityTrustResourceUrl(this.srcEmbeddable() ? src : 'about:blank');\n });\n\n /** Effective content: direct content input or fetched content */\n protected effectiveContent = computed(() => {\n return this.content() || this.fetchedContent();\n });\n\n /** Image transform style for zoom */\n protected imageTransform = computed(() => {\n const z = this.zoom() / 100;\n return `scale(${z})`;\n });\n\n /** Whether zoom controls should be shown */\n protected showZoom = computed(() => {\n const t = this.resolvedType();\n return t === 'image';\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n constructor() {\n // Load whenever the document changes — not just once. A new `src` starts a\n // fresh load (and cancels the one still in flight, so a slow old response\n // can't overwrite the new document), resets any earlier error, and re-enters\n // the loading state; a `content` string short-circuits all of that.\n effect((onCleanup) => {\n const type = this.resolvedType();\n const src = this.src();\n const content = this.content();\n const controller = typeof AbortController === 'function' ? new AbortController() : null;\n onCleanup(() => controller?.abort());\n untracked(() => this.load(type, src, content, controller));\n });\n }\n\n private load(\n type: DocumentType | 'unknown',\n src: string,\n content: string,\n controller: AbortController | null,\n ): void {\n this.error.set(null);\n this.fetchedContent.set('');\n\n if (content) {\n this.loading.set(false);\n return;\n }\n\n if (!src) {\n this.loading.set(false);\n this.error.set('No source or content provided');\n return;\n }\n\n // PDF and images are embedded directly — no fetch needed. They do have to be\n // embeddable, though: a `javascript:` (or other exotic) URL is refused up\n // front instead of being handed to an iframe.\n if (type === 'pdf' || type === 'image') {\n if (!this.srcEmbeddable()) {\n this.loading.set(false);\n this.error.set(this.blockedUrlMessage());\n return;\n }\n // Not \"loading\": the frame/img element only exists once loading is off,\n // and it is its own load event that would end the state — a deadlock.\n this.loading.set(false);\n return;\n }\n\n // Fetch text-based content\n if (type === 'markdown' || type === 'text' || type === 'code') {\n this.loading.set(true);\n this.fetchContent(src, controller);\n } else {\n this.loading.set(false);\n }\n }\n\n ngOnDestroy(): void {\n if (this.fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n // ── Actions ──────────────────────────────────────────────────────────────\n\n protected zoomIn(): void {\n this.zoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.zoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.zoom.set(100);\n }\n\n protected download(): void {\n const src = this.src();\n // A download is a click the user never sees the target of — only URLs that\n // could be embedded are worth clicking.\n if (!src || !this.srcEmbeddable()) return;\n const a = document.createElement('a');\n a.href = src;\n a.download = this.displayName();\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected toggleFullscreen(): void {\n const el = this.elementRef.nativeElement as HTMLElement;\n if (!document.fullscreenElement) {\n el.requestFullscreen?.();\n this.isFullscreen.set(true);\n } else {\n document.exitFullscreen?.();\n this.isFullscreen.set(false);\n }\n\n if (!this.fullscreenHandler) {\n this.fullscreenHandler = () => {\n this.isFullscreen.set(!!document.fullscreenElement);\n };\n document.addEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n protected onIframeLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageError(): void {\n this.loading.set(false);\n this.error.set('Failed to load image');\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private extractExtension(url: string): string | null {\n if (!url) return null;\n try {\n const pathname = new URL(url, 'http://localhost').pathname;\n const parts = pathname.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase() : null;\n } catch {\n const parts = url.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase().split('?')[0] : null;\n }\n }\n\n private async fetchContent(url: string, controller: AbortController | null): Promise<void> {\n if (typeof fetch !== 'function') {\n this.loading.set(false);\n this.error.set('Failed to load document: fetch is not available');\n return;\n }\n try {\n const response = await fetch(url, controller ? { signal: controller.signal } : undefined);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n const text = await response.text();\n // A response for a document that has since been replaced is dropped: the\n // effect that replaced it aborted this controller and started its own load.\n if (controller?.signal.aborted) return;\n this.fetchedContent.set(text);\n this.loading.set(false);\n } catch (err) {\n if (controller?.signal.aborted) return;\n this.loading.set(false);\n this.error.set(`${this.loadErrorLabel()} ${err instanceof Error ? err.message : 'Unknown error'}`);\n }\n }\n}\n","<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -2,7 +2,8 @@ import * as i0 from "@angular/core";
|
|
|
2
2
|
import { ChangeDetectionStrategy, Component, HostListener, computed, effect, inject, input, output, signal, untracked } from "@angular/core";
|
|
3
3
|
import * as i1 from "@angular/cdk/a11y";
|
|
4
4
|
import { A11yModule } from "@angular/cdk/a11y";
|
|
5
|
-
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
5
|
+
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
6
|
+
import { ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
|
|
6
7
|
import { OverlayStackService } from "@lifecorner/angular-ui-kit/internal/overlay";
|
|
7
8
|
const CLOSE_ANIMATION_MS = 250;
|
|
8
9
|
var DrawerComponent = class DrawerComponent {
|
|
@@ -200,6 +201,7 @@ var DrawerComponent = class DrawerComponent {
|
|
|
200
201
|
]
|
|
201
202
|
}
|
|
202
203
|
],
|
|
204
|
+
viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],
|
|
203
205
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
204
206
|
});
|
|
205
207
|
};
|
|
@@ -214,6 +216,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
214
216
|
selector: "lc-drawer",
|
|
215
217
|
standalone: true,
|
|
216
218
|
imports: [A11yModule, IconComponent],
|
|
219
|
+
viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],
|
|
217
220
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
218
221
|
template: "@if (_visible()) {\n <div class=\"lc-drawer\">\n <!-- Overlay -->\n @if (hasOverlay()) {\n <div\n class=\"lc-drawer__overlay\"\n [class.lc-drawer__overlay--visible]=\"open()\"\n (click)=\"onOverlayClick($event)\"\n aria-hidden=\"true\"\n ></div>\n }\n\n <!-- Panel. Focus moves in on open and back to the opener when the panel\n is torn down (cdkTrapFocusAutoCapture); focus is only trapped while\n the backdrop makes the page behind inert. -->\n <aside\n [class]=\"panelClasses()\"\n [style.width]=\"widthValue()\"\n role=\"dialog\"\n [attr.aria-label]=\"ariaLabel() || heading() || 'Drawer'\"\n [attr.aria-modal]=\"hasOverlay() ? 'true' : null\"\n [cdkTrapFocus]=\"hasOverlay()\"\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n <!-- Header -->\n @if (heading()) {\n <header class=\"lc-drawer__header\">\n <h2 class=\"lc-drawer__heading\">{{ heading() }}</h2>\n <button\n type=\"button\"\n class=\"lc-drawer__close\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n </header>\n } @else {\n <button\n type=\"button\"\n class=\"lc-drawer__close lc-drawer__close--floating\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n }\n\n <!-- Projected content -->\n <div class=\"lc-drawer__body\">\n <ng-content />\n </div>\n </aside>\n </div>\n}\n",
|
|
219
222
|
styles: ["@charset \"UTF-8\";.lc-drawer{position:fixed;inset:0;z-index:1100;pointer-events:none}.lc-drawer__overlay{position:absolute;inset:0;background:#0000;transition:background .25s ease;pointer-events:auto;cursor:pointer}.lc-drawer__overlay--visible{background:#00000073}.lc-drawer__panel{position:absolute;top:0;bottom:0;display:flex;flex-direction:column;background-color:var(--color-surface);background-image:var(--gradient-surface);box-shadow:var(--elevation-4);pointer-events:auto;transition:transform .25s cubic-bezier(.4,0,.2,1);max-width:90vw;overflow:hidden}.lc-drawer__panel--right{right:0;transform:translate(100%);border-left:1px solid var(--color-border-strong)}.lc-drawer__panel--left{left:0;transform:translate(-100%);border-right:1px solid var(--color-border-strong)}.lc-drawer__panel--open{transform:translate(0)}.lc-drawer__header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--color-divider);flex-shrink:0}.lc-drawer__heading{margin:0;font-size:1rem;font-weight:600;color:var(--color-text-primary);letter-spacing:-.01em}.lc-drawer__close{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;background:none;border:none;border-radius:var(--border-radius-md, .375rem);color:var(--color-text-tertiary);cursor:pointer;transition:background .15s,color .15s}.lc-drawer__close:hover{background:var(--color-surface-hover);color:var(--color-text-primary)}.lc-drawer__close:focus-visible{outline:2px solid var(--color-primary-500, #208497);outline-offset:2px}.lc-drawer__close--floating{position:absolute;top:.75rem;right:.75rem;z-index:1}.lc-drawer__body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch}@media(max-width:640px){.lc-drawer__panel{max-width:100vw;width:100vw!important}}\n"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-drawer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/drawer/drawer.component.ts","../../../../libs/angular-ui-kit/src/lib/drawer/drawer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n signal,\n computed,\n effect,\n untracked,\n inject,\n HostListener,\n OnDestroy,\n} from '@angular/core';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\n\nexport type DrawerPosition = 'left' | 'right';\nexport type DrawerSize = 'sm' | 'md' | 'lg' | 'xl';\n\n/** Duration of the slide-out transition (see drawer.component.scss). */\nconst CLOSE_ANIMATION_MS = 250;\n\n/**\n * Drawer component for slide-out overlay panels.\n *\n * Features:\n * - Slide-in from left or right\n * - Size presets (sm, md, lg, xl)\n * - Optional backdrop overlay with click-to-close\n * - Close on Escape key support\n * - Heading text display\n * - Content projection for arbitrary body content\n * - Accessible with ARIA dialog role, focus trap and focus restore\n *\n * @example\n * ```html\n * <lc-drawer [open]=\"showPanel\" position=\"right\" size=\"md\"\n * heading=\"Settings\" (closed)=\"showPanel = false\">\n * <p>Any content goes here</p>\n * </lc-drawer>\n * ```\n */\n@Component({\n selector: 'lc-drawer',\n standalone: true,\n imports: [A11yModule, IconComponent],\n templateUrl: './drawer.component.html',\n styleUrl: './drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DrawerComponent implements OnDestroy {\n private static nextId = 0;\n\n /** Whether the drawer is open. */\n readonly open = input<boolean>(false);\n\n /** Slide-in direction. */\n readonly position = input<DrawerPosition>('right');\n\n /** Predefined width (sm=320, md=400, lg=520, xl=640). */\n readonly size = input<DrawerSize>('md');\n\n /** Optional heading shown in the drawer header. */\n readonly heading = input<string>('');\n\n /** Whether the overlay backdrop is shown. */\n readonly hasOverlay = input<boolean>(true);\n\n /** Whether clicking the overlay closes the drawer. */\n readonly closeOnOverlayClick = input<boolean>(true);\n\n /** Whether pressing Escape closes the drawer. */\n readonly closeOnEscape = input<boolean>(true);\n\n /** ARIA label for assistive technology. */\n readonly ariaLabel = input<string>('');\n\n /** Accessible name of the close button. */\n readonly closeAriaLabel = input('Close drawer');\n\n /** Emitted when the drawer should close. */\n readonly closed = output<void>();\n\n /** Internal visibility — stays true during the slide-out animation. */\n protected _visible = signal(false);\n\n /** Width token → CSS. */\n protected readonly widthValue = computed(() => {\n const map: Record<DrawerSize, string> = {\n sm: '320px',\n md: '400px',\n lg: '520px',\n xl: '640px',\n };\n return map[this.size()];\n });\n\n protected readonly panelClasses = computed(() => {\n const cls = ['lc-drawer__panel', `lc-drawer__panel--${this.position()}`];\n if (this.open()) cls.push('lc-drawer__panel--open');\n return cls.join(' ');\n });\n\n private readonly overlayStack = inject(OverlayStackService);\n /** Identifies this instance in the overlay stack. */\n private readonly drawerId = `lc-drawer-${++DrawerComponent.nextId}`;\n private closeTimer?: ReturnType<typeof setTimeout>;\n private originalOverflow?: string;\n\n constructor() {\n effect(() => {\n const isOpen = this.open();\n untracked(() => {\n if (isOpen) {\n // Re-opening within the close animation must cancel the pending hide,\n // otherwise the stale timer hides the drawer and unlocks scroll while\n // `open()` is still true.\n this.clearCloseTimer();\n this._visible.set(true);\n this.lockScroll();\n this.overlayStack.push(this.drawerId);\n } else if (this._visible()) {\n this.overlayStack.remove(this.drawerId);\n // Keep the panel mounted until the slide-out transition has finished\n this.closeTimer = setTimeout(() => {\n this.closeTimer = undefined;\n this._visible.set(false);\n this.unlockScroll();\n }, CLOSE_ANIMATION_MS);\n }\n });\n });\n }\n\n ngOnDestroy(): void {\n // Navigating away while open must not leave the page scroll-locked\n this.clearCloseTimer();\n this.overlayStack.remove(this.drawerId);\n this.unlockScroll();\n }\n\n /**\n * Handle backdrop click. Ignored while another overlay (menu, popover, …)\n * sits above this drawer — the click belongs to that layer.\n */\n protected onOverlayClick(event: MouseEvent): void {\n if (!this.overlayStack.claim(this.drawerId, event)) return;\n if (this.closeOnOverlayClick()) {\n this.close();\n }\n }\n\n /** Handle close action. */\n protected close(): void {\n this.closed.emit();\n }\n\n /**\n * Escape closes the drawer only while it is the top-most overlay; the event\n * is consumed either way so overlays underneath stay open.\n */\n @HostListener('document:keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape' || !this.open()) return;\n if (!this.overlayStack.claim(this.drawerId, event)) return;\n event.stopPropagation();\n if (this.closeOnEscape()) {\n this.close();\n }\n }\n\n private lockScroll(): void {\n if (typeof document === 'undefined' || this.originalOverflow !== undefined) return;\n this.originalOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n }\n\n private unlockScroll(): void {\n if (typeof document === 'undefined' || this.originalOverflow === undefined) return;\n document.body.style.overflow = this.originalOverflow;\n this.originalOverflow = undefined;\n }\n\n private clearCloseTimer(): void {\n if (this.closeTimer !== undefined) {\n clearTimeout(this.closeTimer);\n this.closeTimer = undefined;\n }\n }\n}\n","@if (_visible()) {\n <div class=\"lc-drawer\">\n <!-- Overlay -->\n @if (hasOverlay()) {\n <div\n class=\"lc-drawer__overlay\"\n [class.lc-drawer__overlay--visible]=\"open()\"\n (click)=\"onOverlayClick($event)\"\n aria-hidden=\"true\"\n ></div>\n }\n\n <!-- Panel. Focus moves in on open and back to the opener when the panel\n is torn down (cdkTrapFocusAutoCapture); focus is only trapped while\n the backdrop makes the page behind inert. -->\n <aside\n [class]=\"panelClasses()\"\n [style.width]=\"widthValue()\"\n role=\"dialog\"\n [attr.aria-label]=\"ariaLabel() || heading() || 'Drawer'\"\n [attr.aria-modal]=\"hasOverlay() ? 'true' : null\"\n [cdkTrapFocus]=\"hasOverlay()\"\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n <!-- Header -->\n @if (heading()) {\n <header class=\"lc-drawer__header\">\n <h2 class=\"lc-drawer__heading\">{{ heading() }}</h2>\n <button\n type=\"button\"\n class=\"lc-drawer__close\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n </header>\n } @else {\n <button\n type=\"button\"\n class=\"lc-drawer__close lc-drawer__close--floating\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n }\n\n <!-- Projected content -->\n <div class=\"lc-drawer__body\">\n <ng-content />\n </div>\n </aside>\n </div>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-drawer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/drawer/drawer.component.ts","../../../../libs/angular-ui-kit/src/lib/drawer/drawer.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n signal,\n computed,\n effect,\n untracked,\n inject,\n HostListener,\n OnDestroy,\n} from '@angular/core';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\n\nexport type DrawerPosition = 'left' | 'right';\nexport type DrawerSize = 'sm' | 'md' | 'lg' | 'xl';\n\n/** Duration of the slide-out transition (see drawer.component.scss). */\nconst CLOSE_ANIMATION_MS = 250;\n\n/**\n * Drawer component for slide-out overlay panels.\n *\n * Features:\n * - Slide-in from left or right\n * - Size presets (sm, md, lg, xl)\n * - Optional backdrop overlay with click-to-close\n * - Close on Escape key support\n * - Heading text display\n * - Content projection for arbitrary body content\n * - Accessible with ARIA dialog role, focus trap and focus restore\n *\n * @example\n * ```html\n * <lc-drawer [open]=\"showPanel\" position=\"right\" size=\"md\"\n * heading=\"Settings\" (closed)=\"showPanel = false\">\n * <p>Any content goes here</p>\n * </lc-drawer>\n * ```\n */\n@Component({\n selector: 'lc-drawer',\n standalone: true,\n imports: [A11yModule, IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_X_MARK])],\n templateUrl: './drawer.component.html',\n styleUrl: './drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DrawerComponent implements OnDestroy {\n private static nextId = 0;\n\n /** Whether the drawer is open. */\n readonly open = input<boolean>(false);\n\n /** Slide-in direction. */\n readonly position = input<DrawerPosition>('right');\n\n /** Predefined width (sm=320, md=400, lg=520, xl=640). */\n readonly size = input<DrawerSize>('md');\n\n /** Optional heading shown in the drawer header. */\n readonly heading = input<string>('');\n\n /** Whether the overlay backdrop is shown. */\n readonly hasOverlay = input<boolean>(true);\n\n /** Whether clicking the overlay closes the drawer. */\n readonly closeOnOverlayClick = input<boolean>(true);\n\n /** Whether pressing Escape closes the drawer. */\n readonly closeOnEscape = input<boolean>(true);\n\n /** ARIA label for assistive technology. */\n readonly ariaLabel = input<string>('');\n\n /** Accessible name of the close button. */\n readonly closeAriaLabel = input('Close drawer');\n\n /** Emitted when the drawer should close. */\n readonly closed = output<void>();\n\n /** Internal visibility — stays true during the slide-out animation. */\n protected _visible = signal(false);\n\n /** Width token → CSS. */\n protected readonly widthValue = computed(() => {\n const map: Record<DrawerSize, string> = {\n sm: '320px',\n md: '400px',\n lg: '520px',\n xl: '640px',\n };\n return map[this.size()];\n });\n\n protected readonly panelClasses = computed(() => {\n const cls = ['lc-drawer__panel', `lc-drawer__panel--${this.position()}`];\n if (this.open()) cls.push('lc-drawer__panel--open');\n return cls.join(' ');\n });\n\n private readonly overlayStack = inject(OverlayStackService);\n /** Identifies this instance in the overlay stack. */\n private readonly drawerId = `lc-drawer-${++DrawerComponent.nextId}`;\n private closeTimer?: ReturnType<typeof setTimeout>;\n private originalOverflow?: string;\n\n constructor() {\n effect(() => {\n const isOpen = this.open();\n untracked(() => {\n if (isOpen) {\n // Re-opening within the close animation must cancel the pending hide,\n // otherwise the stale timer hides the drawer and unlocks scroll while\n // `open()` is still true.\n this.clearCloseTimer();\n this._visible.set(true);\n this.lockScroll();\n this.overlayStack.push(this.drawerId);\n } else if (this._visible()) {\n this.overlayStack.remove(this.drawerId);\n // Keep the panel mounted until the slide-out transition has finished\n this.closeTimer = setTimeout(() => {\n this.closeTimer = undefined;\n this._visible.set(false);\n this.unlockScroll();\n }, CLOSE_ANIMATION_MS);\n }\n });\n });\n }\n\n ngOnDestroy(): void {\n // Navigating away while open must not leave the page scroll-locked\n this.clearCloseTimer();\n this.overlayStack.remove(this.drawerId);\n this.unlockScroll();\n }\n\n /**\n * Handle backdrop click. Ignored while another overlay (menu, popover, …)\n * sits above this drawer — the click belongs to that layer.\n */\n protected onOverlayClick(event: MouseEvent): void {\n if (!this.overlayStack.claim(this.drawerId, event)) return;\n if (this.closeOnOverlayClick()) {\n this.close();\n }\n }\n\n /** Handle close action. */\n protected close(): void {\n this.closed.emit();\n }\n\n /**\n * Escape closes the drawer only while it is the top-most overlay; the event\n * is consumed either way so overlays underneath stay open.\n */\n @HostListener('document:keydown', ['$event'])\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape' || !this.open()) return;\n if (!this.overlayStack.claim(this.drawerId, event)) return;\n event.stopPropagation();\n if (this.closeOnEscape()) {\n this.close();\n }\n }\n\n private lockScroll(): void {\n if (typeof document === 'undefined' || this.originalOverflow !== undefined) return;\n this.originalOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n }\n\n private unlockScroll(): void {\n if (typeof document === 'undefined' || this.originalOverflow === undefined) return;\n document.body.style.overflow = this.originalOverflow;\n this.originalOverflow = undefined;\n }\n\n private clearCloseTimer(): void {\n if (this.closeTimer !== undefined) {\n clearTimeout(this.closeTimer);\n this.closeTimer = undefined;\n }\n }\n}\n","@if (_visible()) {\n <div class=\"lc-drawer\">\n <!-- Overlay -->\n @if (hasOverlay()) {\n <div\n class=\"lc-drawer__overlay\"\n [class.lc-drawer__overlay--visible]=\"open()\"\n (click)=\"onOverlayClick($event)\"\n aria-hidden=\"true\"\n ></div>\n }\n\n <!-- Panel. Focus moves in on open and back to the opener when the panel\n is torn down (cdkTrapFocusAutoCapture); focus is only trapped while\n the backdrop makes the page behind inert. -->\n <aside\n [class]=\"panelClasses()\"\n [style.width]=\"widthValue()\"\n role=\"dialog\"\n [attr.aria-label]=\"ariaLabel() || heading() || 'Drawer'\"\n [attr.aria-modal]=\"hasOverlay() ? 'true' : null\"\n [cdkTrapFocus]=\"hasOverlay()\"\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n <!-- Header -->\n @if (heading()) {\n <header class=\"lc-drawer__header\">\n <h2 class=\"lc-drawer__heading\">{{ heading() }}</h2>\n <button\n type=\"button\"\n class=\"lc-drawer__close\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n </header>\n } @else {\n <button\n type=\"button\"\n class=\"lc-drawer__close lc-drawer__close--floating\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n }\n\n <!-- Projected content -->\n <div class=\"lc-drawer__body\">\n <ng-content />\n </div>\n </aside>\n </div>\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,8 @@ import { ChangeDetectionStrategy, Component, computed, effect, forwardRef, input
|
|
|
3
3
|
import { CommonModule } from "@angular/common";
|
|
4
4
|
import { NG_VALUE_ACCESSOR } from "@angular/forms";
|
|
5
5
|
import { InputComponent } from "@lifecorner/angular-ui-kit/input";
|
|
6
|
+
import { ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
7
|
+
import { ICON_MAIL } from "@lifecorner/angular-ui-kit/icons";
|
|
6
8
|
var EmailInputComponent = class EmailInputComponent {
|
|
7
9
|
label = input("Email", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
|
|
8
10
|
placeholder = input("you@example.com", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
|
|
@@ -307,6 +309,7 @@ var EmailInputComponent = class EmailInputComponent {
|
|
|
307
309
|
"enterPressed"
|
|
308
310
|
]
|
|
309
311
|
}],
|
|
312
|
+
viewProviders: [ɵprovideKitIcons([ICON_MAIL])],
|
|
310
313
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
311
314
|
});
|
|
312
315
|
};
|
|
@@ -320,6 +323,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
320
323
|
args: [{
|
|
321
324
|
selector: "lc-email-input",
|
|
322
325
|
imports: [CommonModule, InputComponent],
|
|
326
|
+
viewProviders: [ɵprovideKitIcons([ICON_MAIL])],
|
|
323
327
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
324
328
|
providers: [{
|
|
325
329
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-email-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/email-input/email-input.component.ts","../../../../libs/angular-ui-kit/src/lib/email-input/email-input.component.html"],"sourcesContent":["/* eslint-disable @typescript-eslint/member-ordering */\n/* ControlValueAccessor implementation requires specific method placement */\n\nimport {\n Component,\n input,\n output,\n signal,\n computed,\n ChangeDetectionStrategy,\n forwardRef,\n effect,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { InputAutocapitalize, InputComponent, InputEnterKeyHint } from '@lifecorner/angular-ui-kit/input';\n\n/**\n * Email input component with built-in RFC 5322 validation.\n *\n * Features:\n * - RFC 5322 regex email format validation\n * - Real-time validation feedback on input\n * - Valid/invalid visual indicators\n * - Size presets (xs, sm, md, lg)\n * - Helper text and error message display\n * - Disabled and readonly states (read-only addresses can wrap: `readonlyWrap`)\n * - Optional leading mail icon (`showIcon`)\n * - Autofill and keyboard hints: `autocomplete`, `name`, `autocapitalize`, `spellcheck`, `enterkeyhint`\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-email-input label=\"Email\" placeholder=\"you@example.com\" [(ngModel)]=\"email\" />\n * ```\n */\n@Component({\n selector: 'lc-email-input',\n imports: [CommonModule, InputComponent],\n templateUrl: './email-input.component.html',\n styleUrl: './email-input.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => EmailInputComponent),\n multi: true,\n },\n ],\n})\nexport class EmailInputComponent implements ControlValueAccessor {\n // Input Properties\n /**\n * Input label (displayed above input)\n * @default 'Email'\n */\n readonly label = input<string>('Email');\n\n /**\n * Placeholder text\n * @default 'you@example.com'\n */\n readonly placeholder = input<string>('you@example.com');\n\n /**\n * Size of the input\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the input is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Whether the input is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Custom error message (overrides validation error)\n */\n readonly error = input<string>();\n\n /**\n * Helper text to display below input\n */\n readonly helperText = input<string>();\n\n /**\n * Whether to show validation feedback immediately\n * @default false (validates on blur)\n */\n readonly validateOnInput = input<boolean>(false);\n\n /**\n * ARIA label for accessibility\n */\n readonly ariaLabel = input<string>();\n\n /**\n * Native `autocomplete` (input purpose, WCAG 1.3.5): `email` (registration),\n * `username` (the address as the sign-in name), `username webauthn` (sign-in:\n * the browser offers passkeys in this field). Unset = no attribute.\n */\n readonly autocomplete = input<string>();\n\n /** Native `name` of the inner input. Unset = no attribute. */\n readonly name = input<string>();\n\n /** Native `autocapitalize` (`off` keeps keyboards from capitalising the address). Unset = no attribute. */\n readonly autocapitalize = input<InputAutocapitalize>();\n\n /** Native `spellcheck`. Unset = no attribute. */\n readonly spellcheck = input<boolean>();\n\n /** Native `enterkeyhint`: the label of the on-screen keyboard's Enter key. Unset = no attribute. */\n readonly enterkeyhint = input<InputEnterKeyHint>();\n\n /**\n * Show the leading mail icon. `false` gives the address the icon's width\n * (about 36 px) — long addresses fit on a phone.\n * @default true\n */\n readonly showIcon = input<boolean>(true);\n\n /**\n * While `readonly`: the address wraps onto more lines instead of being\n * clipped. A hidden native input keeps value, `name` and `autocomplete`\n * (e.g. `username` for password managers on a password page).\n * @default false\n */\n readonly readonlyWrap = input<boolean>(false);\n\n /**\n * Show the `*` of a `required` field; `false` keeps the required semantics without it.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /** Error shown for an empty `required` field. */\n readonly requiredMessage = input('Email address is required');\n\n /** Error shown for a malformed address. */\n readonly invalidMessage = input('Please enter a valid email address');\n\n // Output Events\n /**\n * Emitted when email value changes\n */\n readonly valueChange = output<string>();\n\n /**\n * Emitted when validation state changes\n */\n readonly validationChange = output<{ valid: boolean; error?: string }>();\n\n /**\n * Emitted when input receives focus\n */\n readonly focused = output<void>();\n\n /**\n * Emitted when input loses focus\n */\n readonly blurred = output<void>();\n\n /**\n * Emitted when Enter key is pressed\n */\n readonly enterPressed = output<void>();\n\n // Internal state\n readonly value = signal<string>('');\n private readonly touched = signal<boolean>(false);\n private readonly _formDisabled = signal<boolean>(false);\n\n /**\n * RFC 5322 compliant email regex (simplified but robust)\n * Validates: local-part@domain\n * - Local part: alphanumeric, dots, hyphens, underscores, plus signs\n * - Domain: alphanumeric, dots, hyphens with valid TLD\n */\n private readonly EMAIL_REGEX =\n /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\n // ControlValueAccessor callbacks\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n /**\n * Computed validation state\n */\n protected readonly isValid = computed(() => {\n const val = this.value();\n if (!val) return this.required() ? false : true;\n return this.EMAIL_REGEX.test(val);\n });\n\n /**\n * Computed error message\n */\n protected readonly validationError = computed(() => {\n // Custom error takes precedence\n if (this.error()) return this.error();\n\n // Show validation errors only after touched or validateOnInput\n if (!this.touched() && !this.validateOnInput()) return undefined;\n\n const val = this.value();\n if (this.required() && !val) {\n return this.requiredMessage();\n }\n if (val && !this.isValid()) {\n return this.invalidMessage();\n }\n return undefined;\n });\n\n /**\n * Computed disabled state\n */\n protected get isDisabled(): boolean {\n return this.disabled() || this._formDisabled();\n }\n\n constructor() {\n // Emit validation state changes\n effect(() => {\n const valid = this.isValid();\n const error = this.validationError();\n this.validationChange.emit({ valid, error });\n });\n }\n\n // ===== ControlValueAccessor implementation =====\n\n writeValue(value: string): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this._formDisabled.set(isDisabled);\n }\n\n // ===== Public API methods =====\n\n /**\n * Public method to trigger validation\n */\n public validate(): boolean {\n this.touched.set(true);\n return this.isValid();\n }\n\n /**\n * Public method to reset validation state\n */\n public resetValidation(): void {\n this.touched.set(false);\n }\n\n // ===== Event handlers (protected) =====\n\n /**\n * Handles value changes from base input\n */\n protected onValueChange(newValue: string): void {\n this.value.set(newValue);\n this.onChange(newValue);\n this.valueChange.emit(newValue);\n }\n\n /**\n * Handles focus events\n */\n protected onFocus(): void {\n this.focused.emit();\n }\n\n /**\n * Handles blur events\n */\n protected onBlur(): void {\n this.touched.set(true);\n this.onTouched();\n this.blurred.emit();\n }\n\n /**\n * Handles Enter key press\n */\n protected onEnterPressed(): void {\n this.enterPressed.emit();\n }\n}\n","<lc-input\n [label]=\"label()\"\n [placeholder]=\"placeholder()\"\n type=\"email\"\n [size]=\"size()\"\n [value]=\"value()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [readonlyWrap]=\"readonlyWrap()\"\n [required]=\"required()\"\n [requiredIndicator]=\"requiredIndicator()\"\n [error]=\"validationError()\"\n [helperText]=\"helperText()\"\n [iconBefore]=\"showIcon() ? 'envelope' : undefined\"\n [ariaLabel]=\"ariaLabel() || label()\"\n [autocomplete]=\"autocomplete()\"\n [name]=\"name()\"\n [autocapitalize]=\"autocapitalize()\"\n [spellcheck]=\"spellcheck()\"\n [enterkeyhint]=\"enterkeyhint()\"\n (valueChange)=\"onValueChange($event)\"\n (focused)=\"onFocus()\"\n (blurred)=\"onBlur()\"\n (enterPressed)=\"onEnterPressed()\"\n/>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-email-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/email-input/email-input.component.ts","../../../../libs/angular-ui-kit/src/lib/email-input/email-input.component.html"],"sourcesContent":["/* eslint-disable @typescript-eslint/member-ordering */\n/* ControlValueAccessor implementation requires specific method placement */\n\nimport {\n Component,\n input,\n output,\n signal,\n computed,\n ChangeDetectionStrategy,\n forwardRef,\n effect,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { InputAutocapitalize, InputComponent, InputEnterKeyHint } from '@lifecorner/angular-ui-kit/input';\nimport { ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_MAIL } from '@lifecorner/angular-ui-kit/icons';\n\n/**\n * Email input component with built-in RFC 5322 validation.\n *\n * Features:\n * - RFC 5322 regex email format validation\n * - Real-time validation feedback on input\n * - Valid/invalid visual indicators\n * - Size presets (xs, sm, md, lg)\n * - Helper text and error message display\n * - Disabled and readonly states (read-only addresses can wrap: `readonlyWrap`)\n * - Optional leading mail icon (`showIcon`)\n * - Autofill and keyboard hints: `autocomplete`, `name`, `autocapitalize`, `spellcheck`, `enterkeyhint`\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-email-input label=\"Email\" placeholder=\"you@example.com\" [(ngModel)]=\"email\" />\n * ```\n */\n@Component({\n selector: 'lc-email-input',\n imports: [CommonModule, InputComponent],\n // The `envelope` icon it hands to lc-input (an alias of the Tabler `mail`).\n viewProviders: [ɵprovideKitIcons([ICON_MAIL])],\n templateUrl: './email-input.component.html',\n styleUrl: './email-input.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => EmailInputComponent),\n multi: true,\n },\n ],\n})\nexport class EmailInputComponent implements ControlValueAccessor {\n // Input Properties\n /**\n * Input label (displayed above input)\n * @default 'Email'\n */\n readonly label = input<string>('Email');\n\n /**\n * Placeholder text\n * @default 'you@example.com'\n */\n readonly placeholder = input<string>('you@example.com');\n\n /**\n * Size of the input\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the input is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Whether the input is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Custom error message (overrides validation error)\n */\n readonly error = input<string>();\n\n /**\n * Helper text to display below input\n */\n readonly helperText = input<string>();\n\n /**\n * Whether to show validation feedback immediately\n * @default false (validates on blur)\n */\n readonly validateOnInput = input<boolean>(false);\n\n /**\n * ARIA label for accessibility\n */\n readonly ariaLabel = input<string>();\n\n /**\n * Native `autocomplete` (input purpose, WCAG 1.3.5): `email` (registration),\n * `username` (the address as the sign-in name), `username webauthn` (sign-in:\n * the browser offers passkeys in this field). Unset = no attribute.\n */\n readonly autocomplete = input<string>();\n\n /** Native `name` of the inner input. Unset = no attribute. */\n readonly name = input<string>();\n\n /** Native `autocapitalize` (`off` keeps keyboards from capitalising the address). Unset = no attribute. */\n readonly autocapitalize = input<InputAutocapitalize>();\n\n /** Native `spellcheck`. Unset = no attribute. */\n readonly spellcheck = input<boolean>();\n\n /** Native `enterkeyhint`: the label of the on-screen keyboard's Enter key. Unset = no attribute. */\n readonly enterkeyhint = input<InputEnterKeyHint>();\n\n /**\n * Show the leading mail icon. `false` gives the address the icon's width\n * (about 36 px) — long addresses fit on a phone.\n * @default true\n */\n readonly showIcon = input<boolean>(true);\n\n /**\n * While `readonly`: the address wraps onto more lines instead of being\n * clipped. A hidden native input keeps value, `name` and `autocomplete`\n * (e.g. `username` for password managers on a password page).\n * @default false\n */\n readonly readonlyWrap = input<boolean>(false);\n\n /**\n * Show the `*` of a `required` field; `false` keeps the required semantics without it.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /** Error shown for an empty `required` field. */\n readonly requiredMessage = input('Email address is required');\n\n /** Error shown for a malformed address. */\n readonly invalidMessage = input('Please enter a valid email address');\n\n // Output Events\n /**\n * Emitted when email value changes\n */\n readonly valueChange = output<string>();\n\n /**\n * Emitted when validation state changes\n */\n readonly validationChange = output<{ valid: boolean; error?: string }>();\n\n /**\n * Emitted when input receives focus\n */\n readonly focused = output<void>();\n\n /**\n * Emitted when input loses focus\n */\n readonly blurred = output<void>();\n\n /**\n * Emitted when Enter key is pressed\n */\n readonly enterPressed = output<void>();\n\n // Internal state\n readonly value = signal<string>('');\n private readonly touched = signal<boolean>(false);\n private readonly _formDisabled = signal<boolean>(false);\n\n /**\n * RFC 5322 compliant email regex (simplified but robust)\n * Validates: local-part@domain\n * - Local part: alphanumeric, dots, hyphens, underscores, plus signs\n * - Domain: alphanumeric, dots, hyphens with valid TLD\n */\n private readonly EMAIL_REGEX =\n /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\n // ControlValueAccessor callbacks\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n /**\n * Computed validation state\n */\n protected readonly isValid = computed(() => {\n const val = this.value();\n if (!val) return this.required() ? false : true;\n return this.EMAIL_REGEX.test(val);\n });\n\n /**\n * Computed error message\n */\n protected readonly validationError = computed(() => {\n // Custom error takes precedence\n if (this.error()) return this.error();\n\n // Show validation errors only after touched or validateOnInput\n if (!this.touched() && !this.validateOnInput()) return undefined;\n\n const val = this.value();\n if (this.required() && !val) {\n return this.requiredMessage();\n }\n if (val && !this.isValid()) {\n return this.invalidMessage();\n }\n return undefined;\n });\n\n /**\n * Computed disabled state\n */\n protected get isDisabled(): boolean {\n return this.disabled() || this._formDisabled();\n }\n\n constructor() {\n // Emit validation state changes\n effect(() => {\n const valid = this.isValid();\n const error = this.validationError();\n this.validationChange.emit({ valid, error });\n });\n }\n\n // ===== ControlValueAccessor implementation =====\n\n writeValue(value: string): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this._formDisabled.set(isDisabled);\n }\n\n // ===== Public API methods =====\n\n /**\n * Public method to trigger validation\n */\n public validate(): boolean {\n this.touched.set(true);\n return this.isValid();\n }\n\n /**\n * Public method to reset validation state\n */\n public resetValidation(): void {\n this.touched.set(false);\n }\n\n // ===== Event handlers (protected) =====\n\n /**\n * Handles value changes from base input\n */\n protected onValueChange(newValue: string): void {\n this.value.set(newValue);\n this.onChange(newValue);\n this.valueChange.emit(newValue);\n }\n\n /**\n * Handles focus events\n */\n protected onFocus(): void {\n this.focused.emit();\n }\n\n /**\n * Handles blur events\n */\n protected onBlur(): void {\n this.touched.set(true);\n this.onTouched();\n this.blurred.emit();\n }\n\n /**\n * Handles Enter key press\n */\n protected onEnterPressed(): void {\n this.enterPressed.emit();\n }\n}\n","<lc-input\n [label]=\"label()\"\n [placeholder]=\"placeholder()\"\n type=\"email\"\n [size]=\"size()\"\n [value]=\"value()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [readonlyWrap]=\"readonlyWrap()\"\n [required]=\"required()\"\n [requiredIndicator]=\"requiredIndicator()\"\n [error]=\"validationError()\"\n [helperText]=\"helperText()\"\n [iconBefore]=\"showIcon() ? 'envelope' : undefined\"\n [ariaLabel]=\"ariaLabel() || label()\"\n [autocomplete]=\"autocomplete()\"\n [name]=\"name()\"\n [autocapitalize]=\"autocapitalize()\"\n [spellcheck]=\"spellcheck()\"\n [enterkeyhint]=\"enterkeyhint()\"\n (valueChange)=\"onValueChange($event)\"\n (focused)=\"onFocus()\"\n (blurred)=\"onBlur()\"\n (enterPressed)=\"onEnterPressed()\"\n/>\n"],"mappings":";;;;;;;AAsDA,IAAa,sBAAb,MAAa,oBAAmB;CAMrB,QAAQ,MAAc,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAM9B,cAAc,MAAc,mBAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAM9C,OAAO,MAAiC,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAM7C,WAAW,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAMhC,WAAW,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAMhC,WAAW,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAKhC,QAAQ,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAKvB,aAAa,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAM5B,kBAAkB,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAKvC,YAAY,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAO3B,eAAe,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,OAAO,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAGtB,iBAAiB,MAA4B,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG7C,aAAa,MAAgB,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAG7B,eAAe,MAA0B,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAOzC,WAAW,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAQ/B,eAAe,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAMpC,oBAAoB,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,kBAAkB,MAAM,6BAA4B,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpD,iBAAiB,MAAM,sCAAqC,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAM5D,cAAc,OAAM;CAKpB,mBAAmB,OAAM;CAKzB,UAAU,OAAM;CAKhB,UAAU,OAAM;CAKhB,eAAe,OAAM;CAGrB,QAAQ,OAAe,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAClB,UAAU,OAAgB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAChC,gBAAgB,OAAgB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAQtC,cACf;CAGM,iBAAyC,CAAiC;CAC1E,kBAA6B,CAAkC;CAKpD,UAAU,eAAc;EACzC,MAAM,MAAM,KAAK,MAAK;EACtB,IAAI,CAAC,KAAK,OAAO,KAAK,SAAQ,IAAK,QAAQ;EAC3C,OAAO,KAAK,YAAY,KAAK,GAAG;CAClC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAKiB,kBAAkB,eAAc;EAEjD,IAAI,KAAK,MAAK,GAAI,OAAO,KAAK,MAAK;EAGnC,IAAI,CAAC,KAAK,QAAO,KAAM,CAAC,KAAK,gBAAe,GAAI,OAAO;EAEvD,MAAM,MAAM,KAAK,MAAK;EACtB,IAAI,KAAK,SAAQ,KAAM,CAAC,KACtB,OAAO,KAAK,gBAAe;EAE7B,IAAI,OAAO,CAAC,KAAK,QAAO,GACtB,OAAO,KAAK,eAAc;CAG9B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAKF,IAAc,aAAU;EACtB,OAAO,KAAK,SAAQ,KAAM,KAAK,cAAa;CAC9C;CAEA,cAAA;EAEE,aAAY;GACV,MAAM,QAAQ,KAAK,QAAO;GAC1B,MAAM,QAAQ,KAAK,gBAAe;GAClC,KAAK,iBAAiB,KAAK;IAAE;IAAO;GAAK,CAAE;EAC7C,CAAC;CACH;CAIA,WAAW,OAAa;EACtB,KAAK,MAAM,IAAI,SAAS,EAAE;CAC5B;CAEA,iBAAiB,IAA2B;EAC1C,KAAK,WAAW;CAClB;CAEA,kBAAkB,IAAc;EAC9B,KAAK,YAAY;CACnB;CAEA,iBAAiB,YAAmB;EAClC,KAAK,cAAc,IAAI,UAAU;CACnC;CAOO,WAAQ;EACb,KAAK,QAAQ,IAAI,IAAI;EACrB,OAAO,KAAK,QAAO;CACrB;CAKO,kBAAe;EACpB,KAAK,QAAQ,IAAI,KAAK;CACxB;CAOU,cAAc,UAAgB;EACtC,KAAK,MAAM,IAAI,QAAQ;EACvB,KAAK,SAAS,QAAQ;EACtB,KAAK,YAAY,KAAK,QAAQ;CAChC;CAKU,UAAO;EACf,KAAK,QAAQ,KAAI;CACnB;CAKU,SAAM;EACd,KAAK,QAAQ,IAAI,IAAI;EACrB,KAAK,UAAS;EACd,KAAK,QAAQ,KAAI;CACnB;CAKU,iBAAc;EACtB,KAAK,aAAa,KAAI;CACxB;CArQW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAmB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,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,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,mBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,aAAA;GAAA,kBAAA;GAAA,SAAA;GAAA,SAAA;GAAA,cAAA;EAAA;EAAA,WARnB,CACT;GACE,SAAS;GACT,aAAa,iBAAiB,mBAAmB;GACjD,OAAO;EACR,CACF;EAAA,UAAA;EAAA,UCpDH;EAyBA,QAAA,CAAA,mCAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDeY;EAAY,GAAA;GAAA,MAAA;GAAA,MAAE;GAAc,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;GAAA,SAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eAEvB,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;EAAC,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAYnC,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAhB/B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAgB,SACjB,CAAC,cAAc,cAAc;GAAC,eAExB,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;GAAC,iBAG7B,wBAAwB;GAAM,WACpC,CACT;IACE,SAAS;IACT,aAAa,iBAAgB,mBAAoB;IACjD,OAAO;GACR,CACF;GAAA,UAAA;GAAA,QAAA,CAAA,mCAAA;EAAA,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, computed, input, output, signal, viewChild } from "@angular/core";
|
|
3
|
-
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
3
|
+
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
4
|
+
import { ICON_FILE, ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
|
|
4
5
|
var FileUploadComponent = class FileUploadComponent {
|
|
5
6
|
accept = input("", ...ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []);
|
|
6
7
|
multiple = input(false, ...ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []);
|
|
@@ -178,6 +179,7 @@ var FileUploadComponent = class FileUploadComponent {
|
|
|
178
179
|
"strict"
|
|
179
180
|
]
|
|
180
181
|
}],
|
|
182
|
+
viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],
|
|
181
183
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
182
184
|
});
|
|
183
185
|
};
|
|
@@ -192,6 +194,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
192
194
|
selector: "lc-file-upload",
|
|
193
195
|
standalone: true,
|
|
194
196
|
imports: [IconComponent],
|
|
197
|
+
viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],
|
|
195
198
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
196
199
|
template: "<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openFilePicker()\"\n (keydown.space)=\"openFilePicker()\"\n [attr.aria-label]=\"hint()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\"></lc-icon>\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n <p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n <p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel()\"\n >\n <lc-icon name=\"x-mark\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n",
|
|
197
200
|
styles: [":host{display:block;width:100%}.file-upload{display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;border:2px dashed var(--color-border-strong);border-radius:var(--border-radius-lg);background-color:var(--color-surface-2);cursor:pointer;transition:border-color .15s ease,background-color .15s ease;text-align:center}.file-upload:hover{border-color:var(--color-primary-500);background-color:var(--color-surface-hover)}.file-upload:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.file-upload--drag-over{border-color:var(--color-primary-500);background-color:var(--color-surface-selected)}.file-upload--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.file-upload__input{position:absolute;width:0;height:0;overflow:hidden;opacity:0}.file-upload__content{display:flex;flex-direction:column;align-items:center;gap:.5rem}.file-upload__icon{--icon-size: 2rem;color:var(--color-primary-500)}.file-upload__hint{margin:0;font-size:.875rem;color:var(--color-text-secondary)}.file-upload__accept{margin:0;font-size:.75rem;color:var(--color-text-tertiary)}.file-upload__list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}.file-upload__file{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--border-radius-md);font-size:.8125rem}.file-upload__file-icon{--icon-size: 1rem;color:var(--color-text-tertiary);flex-shrink:0}.file-upload__file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary)}.file-upload__file-size{flex-shrink:0;color:var(--color-text-tertiary);font-size:.75rem}.file-upload__file-remove{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;--icon-size: .75rem;color:var(--color-text-secondary)}.file-upload__file-remove:hover{background:#9d0e0e2e;color:var(--color-error)}\n"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-file-upload.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.ts","../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n signal,\n computed,\n ElementRef,\n viewChild,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\nexport interface FileUploadFile {\n file: File;\n name: string;\n size: number;\n type: string;\n}\n\n/**\n * File upload / dropzone component with drag & drop support.\n *\n * @example\n * ```html\n * <lc-file-upload\n * accept=\".pdf,.png,.jpg\"\n * [multiple]=\"true\"\n * [maxSizeBytes]=\"5242880\"\n * (filesSelected)=\"onFiles($event)\"\n * ></lc-file-upload>\n * ```\n */\n@Component({\n selector: 'lc-file-upload',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './file-upload.component.html',\n styleUrls: ['./file-upload.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * File upload component with drag-and-drop support.\n *\n * Features:\n * - Drag-and-drop file dropzone with visual feedback\n * - Click-to-browse file selection\n * - File type and size validation\n * - Multi-file upload support\n * - File list display with removal\n * - Disabled state support\n *\n * @example\n * ```html\n * <lc-file-upload accept=\".pdf,.jpg\" [multiple]=\"true\" [maxSizeBytes]=\"5242880\" />\n * ```\n */\nexport class FileUploadComponent {\n /**\n * Accepted file types (e.g. \".pdf,.jpg,image/*\").\n */\n accept = input<string>('');\n\n /**\n * Allow multiple files.\n * @default false\n */\n multiple = input<boolean>(false);\n\n /**\n * Maximum file size in bytes. 0 for unlimited.\n * @default 0\n */\n maxSizeBytes = input<number>(0);\n\n /**\n * Whether the dropzone is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Hint text shown in the dropzone.\n * @default 'Drag & drop files here, or click to browse'\n */\n hint = input<string>('Drag & drop files here, or click to browse');\n\n /** Prefix of the accepted file types line (`accept`). */\n acceptedLabel = input('Accepted:');\n\n /** Prefix of the maximum file size line (`maxSizeBytes`). */\n maxSizeLabel = input('Max:');\n\n /** Accessible name of a selected file's remove button. */\n removeFileAriaLabel = input('Remove file');\n\n /**\n * Emitted with selected files after validation.\n */\n readonly filesSelected = output<FileUploadFile[]>();\n\n /**\n * Emitted when a file is rejected (too large or wrong type).\n */\n readonly fileRejected = output<{ file: File; reason: string }>();\n\n protected isDragOver = signal(false);\n protected selectedFiles = signal<FileUploadFile[]>([]);\n protected readonly fileInputRef = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n protected dropzoneClasses = computed(() => {\n return [\n 'file-upload',\n this.isDragOver() ? 'file-upload--drag-over' : '',\n this.disabled() ? 'file-upload--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this.isDragOver.set(true);\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n if (this.disabled() || !event.dataTransfer?.files.length) return;\n this.processFiles(event.dataTransfer.files);\n }\n\n protected onFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files?.length) {\n this.processFiles(input.files);\n }\n // Reset so the same file can be re-selected\n input.value = '';\n }\n\n protected openFilePicker(): void {\n if (!this.disabled()) {\n this.fileInputRef()?.nativeElement.click();\n }\n }\n\n protected removeFile(index: number): void {\n this.selectedFiles.update((files) => files.filter((_, i) => i !== index));\n this.filesSelected.emit(this.selectedFiles());\n }\n\n protected formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n private processFiles(fileList: FileList): void {\n const files = Array.from(fileList);\n const accepted: FileUploadFile[] = [];\n\n for (const file of files) {\n if (this.maxSizeBytes() > 0 && file.size > this.maxSizeBytes()) {\n this.fileRejected.emit({ file, reason: 'File too large' });\n continue;\n }\n accepted.push({\n file,\n name: file.name,\n size: file.size,\n type: file.type,\n });\n }\n\n if (this.multiple()) {\n this.selectedFiles.update((prev) => [...prev, ...accepted]);\n } else {\n this.selectedFiles.set(accepted.slice(0, 1));\n }\n\n this.filesSelected.emit(this.selectedFiles());\n }\n}\n","<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openFilePicker()\"\n (keydown.space)=\"openFilePicker()\"\n [attr.aria-label]=\"hint()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\"></lc-icon>\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n <p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n <p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel()\"\n >\n <lc-icon name=\"x-mark\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-file-upload.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.ts","../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n signal,\n computed,\n ElementRef,\n viewChild,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_FILE, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\n\nexport interface FileUploadFile {\n file: File;\n name: string;\n size: number;\n type: string;\n}\n\n/**\n * File upload / dropzone component with drag & drop support.\n *\n * @example\n * ```html\n * <lc-file-upload\n * accept=\".pdf,.png,.jpg\"\n * [multiple]=\"true\"\n * [maxSizeBytes]=\"5242880\"\n * (filesSelected)=\"onFiles($event)\"\n * ></lc-file-upload>\n * ```\n */\n@Component({\n selector: 'lc-file-upload',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],\n templateUrl: './file-upload.component.html',\n styleUrls: ['./file-upload.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * File upload component with drag-and-drop support.\n *\n * Features:\n * - Drag-and-drop file dropzone with visual feedback\n * - Click-to-browse file selection\n * - File type and size validation\n * - Multi-file upload support\n * - File list display with removal\n * - Disabled state support\n *\n * @example\n * ```html\n * <lc-file-upload accept=\".pdf,.jpg\" [multiple]=\"true\" [maxSizeBytes]=\"5242880\" />\n * ```\n */\nexport class FileUploadComponent {\n /**\n * Accepted file types (e.g. \".pdf,.jpg,image/*\").\n */\n accept = input<string>('');\n\n /**\n * Allow multiple files.\n * @default false\n */\n multiple = input<boolean>(false);\n\n /**\n * Maximum file size in bytes. 0 for unlimited.\n * @default 0\n */\n maxSizeBytes = input<number>(0);\n\n /**\n * Whether the dropzone is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Hint text shown in the dropzone.\n * @default 'Drag & drop files here, or click to browse'\n */\n hint = input<string>('Drag & drop files here, or click to browse');\n\n /** Prefix of the accepted file types line (`accept`). */\n acceptedLabel = input('Accepted:');\n\n /** Prefix of the maximum file size line (`maxSizeBytes`). */\n maxSizeLabel = input('Max:');\n\n /** Accessible name of a selected file's remove button. */\n removeFileAriaLabel = input('Remove file');\n\n /**\n * Emitted with selected files after validation.\n */\n readonly filesSelected = output<FileUploadFile[]>();\n\n /**\n * Emitted when a file is rejected (too large or wrong type).\n */\n readonly fileRejected = output<{ file: File; reason: string }>();\n\n protected isDragOver = signal(false);\n protected selectedFiles = signal<FileUploadFile[]>([]);\n protected readonly fileInputRef = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n protected dropzoneClasses = computed(() => {\n return [\n 'file-upload',\n this.isDragOver() ? 'file-upload--drag-over' : '',\n this.disabled() ? 'file-upload--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this.isDragOver.set(true);\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n if (this.disabled() || !event.dataTransfer?.files.length) return;\n this.processFiles(event.dataTransfer.files);\n }\n\n protected onFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files?.length) {\n this.processFiles(input.files);\n }\n // Reset so the same file can be re-selected\n input.value = '';\n }\n\n protected openFilePicker(): void {\n if (!this.disabled()) {\n this.fileInputRef()?.nativeElement.click();\n }\n }\n\n protected removeFile(index: number): void {\n this.selectedFiles.update((files) => files.filter((_, i) => i !== index));\n this.filesSelected.emit(this.selectedFiles());\n }\n\n protected formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n private processFiles(fileList: FileList): void {\n const files = Array.from(fileList);\n const accepted: FileUploadFile[] = [];\n\n for (const file of files) {\n if (this.maxSizeBytes() > 0 && file.size > this.maxSizeBytes()) {\n this.fileRejected.emit({ file, reason: 'File too large' });\n continue;\n }\n accepted.push({\n file,\n name: file.name,\n size: file.size,\n type: file.type,\n });\n }\n\n if (this.multiple()) {\n this.selectedFiles.update((prev) => [...prev, ...accepted]);\n } else {\n this.selectedFiles.set(accepted.slice(0, 1));\n }\n\n this.filesSelected.emit(this.selectedFiles());\n }\n}\n","<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openFilePicker()\"\n (keydown.space)=\"openFilePicker()\"\n [attr.aria-label]=\"hint()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\"></lc-icon>\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n <p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n <p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel()\"\n >\n <lc-icon name=\"x-mark\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
|
|
3
|
-
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
3
|
+
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
4
|
+
import { ICON_EXTERNAL_LINK } from "@lifecorner/angular-ui-kit/icons";
|
|
4
5
|
var FooterComponent = class FooterComponent {
|
|
5
6
|
sections = input([], ...ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []);
|
|
6
7
|
copyright = input(...ngDevMode ? [void 0, { debugName: "copyright" }] : /* istanbul ignore next */ []);
|
|
@@ -109,6 +110,7 @@ var FooterComponent = class FooterComponent {
|
|
|
109
110
|
"strict"
|
|
110
111
|
]
|
|
111
112
|
}],
|
|
113
|
+
viewProviders: [ɵprovideKitIcons([ICON_EXTERNAL_LINK])],
|
|
112
114
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
113
115
|
});
|
|
114
116
|
};
|
|
@@ -123,6 +125,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
123
125
|
selector: "lc-footer",
|
|
124
126
|
standalone: true,
|
|
125
127
|
imports: [IconComponent],
|
|
128
|
+
viewProviders: [ɵprovideKitIcons([ICON_EXTERNAL_LINK])],
|
|
126
129
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
127
130
|
template: "<footer [class]=\"footerClasses()\" role=\"contentinfo\">\n @if (sections().length > 0 && !compact()) {\n <nav class=\"footer__sections\" [attr.aria-label]=\"navAriaLabel()\">\n @for (section of sections(); track section.title) {\n <div class=\"footer__section\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"footer__section-title\">{{ section.title }}</h2>\n }\n @case (3) {\n <h3 class=\"footer__section-title\">{{ section.title }}</h3>\n }\n @case (5) {\n <h5 class=\"footer__section-title\">{{ section.title }}</h5>\n }\n @case (6) {\n <h6 class=\"footer__section-title\">{{ section.title }}</h6>\n }\n @default {\n <h4 class=\"footer__section-title\">{{ section.title }}</h4>\n }\n }\n <ul class=\"footer__links\">\n @for (link of section.links; track link.href) {\n <li>\n <a\n class=\"footer__link\"\n [class.footer__link--current]=\"link.current\"\n [href]=\"link.href\"\n [target]=\"link.external ? '_blank' : '_self'\"\n [attr.rel]=\"linkRel(link)\"\n [attr.aria-current]=\"link.current ? 'page' : null\"\n >\n {{ link.label }}\n @if (link.external) {\n <span class=\"footer__sr-only\">{{ externalLinkHint() }}</span>\n <lc-icon\n name=\"external-link\"\n size=\"xs\"\n class=\"footer__external-icon\"\n aria-hidden=\"true\"\n [decorative]=\"true\"\n />\n }\n </a>\n </li>\n }\n </ul>\n </div>\n }\n </nav>\n }\n <div class=\"footer__bottom\">\n <ng-content />\n @if (copyright()) {\n <span class=\"footer__copyright\">{{ copyright() }}</span>\n }\n </div>\n</footer>\n",
|
|
128
131
|
styles: [".footer{padding:var(--lc-density-padding-xl, 2rem) var(--lc-density-padding-lg, 1.5rem) var(--lc-density-padding-lg, 1.5rem);color:var(--color-text-secondary);font-size:var(--typography-font-size-sm, .875rem)}.footer--bordered{border-top:1px solid var(--color-border)}.footer__sections{display:grid;grid-template-columns:repeat(auto-fit,minmax(10rem,1fr));gap:var(--lc-density-gap-xl, 2rem);margin-bottom:var(--lc-density-padding-xl, 2rem)}.footer__section-title{font-size:var(--typography-font-size-sm, .875rem);font-weight:var(--typography-font-weight-semibold, 600);line-height:var(--line-height-normal, 1.5);color:var(--color-text-primary);margin:0 0 .25rem;text-transform:uppercase;letter-spacing:.05em}.footer__links{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}.footer__link{display:inline-flex;align-items:center;gap:.25rem;min-height:var(--size-min-touch-height, 2.75rem);color:var(--color-text-secondary);text-decoration:none;text-underline-offset:.2em;transition:color .15s ease}.footer__link:hover{color:var(--color-text-primary);text-decoration:underline}.footer__link:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px;border-radius:var(--border-radius-sm)}.footer__link--current{color:var(--color-text-primary);font-weight:var(--typography-font-weight-semibold, 600);text-decoration:underline;text-decoration-thickness:2px}.footer__external-icon{opacity:.6}.footer__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.footer__bottom{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem}.footer__copyright{color:var(--color-text-secondary);font-size:var(--typography-font-size-xs, .75rem)}.footer--compact{padding:1rem 1.5rem}.footer--compact .footer__bottom{justify-content:center}.footer--primary{background-color:#071a1f;color:#c1e3e9}.footer--primary .footer__section-title{color:#fff}.footer--primary .footer__link{color:#a3d5df}.footer--primary .footer__link:hover,.footer--primary .footer__link--current{color:#fff}.footer--primary .footer__copyright{color:#84c7d4}.footer--primary.footer--bordered{border-top-color:#144f5b}.footer--dark{background-color:#0c1722;color:#93a4ad}.footer--dark .footer__section-title{color:#f1f6f8}.footer--dark .footer__link{color:#93a4ad}.footer--dark .footer__link:hover,.footer--dark .footer__link--current{color:#f1f6f8}.footer--dark .footer__copyright{color:#93a4ad}.footer--dark.footer--bordered{border-top-color:#ffffff14}.footer--neutral{background-color:var(--color-surface-hover)}\n"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-footer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/footer/footer.component.ts","../../../../libs/angular-ui-kit/src/lib/footer/footer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\nexport interface FooterLink {\n /** Display label */\n label: string;\n /** URL or route */\n href: string;\n /**\n * Opens in a new tab (`target=\"_blank\"`). Screen readers hear the\n * `externalLinkHint` after the label; `rel` defaults to\n * `noopener noreferrer`.\n */\n external?: boolean;\n /** Marks the page the visitor is on (`aria-current=\"page\"`). */\n current?: boolean;\n /**\n * `rel` attribute, e.g. `me` for your own social profiles. Replaces the\n * `noopener noreferrer` default of external links.\n */\n rel?: string;\n}\n\nexport interface FooterSection {\n /** Section heading */\n title: string;\n /** Links in this section */\n links: FooterLink[];\n}\n\nexport type FooterVariant = 'default' | 'primary' | 'dark' | 'neutral';\n\n/** Heading level of the section titles (`headingLevel`). */\nexport type FooterHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\n@Component({\n selector: 'lc-footer',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './footer.component.html',\n styleUrls: ['./footer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Footer component for application-wide bottom navigation.\n *\n * Features:\n * - Multi-section navigation with link groups, inside a labelled `<nav>`\n * (`navAriaLabel`)\n * - Section titles at a configurable heading level (`headingLevel`, default 4)\n * - Links: `current` page (`aria-current`), `rel`, `external` (new tab, with a\n * text hint for screen readers — `externalLinkHint`); 44 px touch targets\n * - Copyright text display\n * - Compact single-row layout option\n * - Color variants (default, primary, dark, neutral)\n * - Optional top border\n *\n * @example\n * ```html\n * <lc-footer [sections]=\"navSections\" copyright=\"© 2026 Company\" />\n * ```\n */\nexport class FooterComponent {\n /** Footer navigation sections */\n sections = input<FooterSection[]>([]);\n\n /** Copyright text */\n copyright = input<string>();\n\n /** Whether to show a top border */\n showBorder = input<boolean>(true);\n\n /** Compact layout (single row) */\n compact = input<boolean>(false);\n\n /** Color variant */\n variant = input<FooterVariant>('default');\n\n /**\n * Heading level of the section titles. Pick the level that continues the\n * page's outline (a page with `h1` + `h2` sections usually wants `2`).\n */\n headingLevel = input<FooterHeadingLevel>(4);\n\n /** Accessible name of the `<nav>` around the sections. */\n navAriaLabel = input<string>('Footer');\n\n /** Visually hidden text read after the label of an `external` link. */\n externalLinkHint = input<string>('(opens in a new tab)');\n\n protected footerClasses = computed(() => {\n const classes = ['footer', `footer--${this.variant()}`];\n if (this.showBorder()) classes.push('footer--bordered');\n if (this.compact()) classes.push('footer--compact');\n return classes.join(' ');\n });\n\n /** `rel` of a link: its own value, else `noopener noreferrer` for external links. */\n protected linkRel(link: FooterLink): string | null {\n return link.rel ?? (link.external ? 'noopener noreferrer' : null);\n }\n\n protected currentYear = new Date().getFullYear();\n}\n","<footer [class]=\"footerClasses()\" role=\"contentinfo\">\n @if (sections().length > 0 && !compact()) {\n <nav class=\"footer__sections\" [attr.aria-label]=\"navAriaLabel()\">\n @for (section of sections(); track section.title) {\n <div class=\"footer__section\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"footer__section-title\">{{ section.title }}</h2>\n }\n @case (3) {\n <h3 class=\"footer__section-title\">{{ section.title }}</h3>\n }\n @case (5) {\n <h5 class=\"footer__section-title\">{{ section.title }}</h5>\n }\n @case (6) {\n <h6 class=\"footer__section-title\">{{ section.title }}</h6>\n }\n @default {\n <h4 class=\"footer__section-title\">{{ section.title }}</h4>\n }\n }\n <ul class=\"footer__links\">\n @for (link of section.links; track link.href) {\n <li>\n <a\n class=\"footer__link\"\n [class.footer__link--current]=\"link.current\"\n [href]=\"link.href\"\n [target]=\"link.external ? '_blank' : '_self'\"\n [attr.rel]=\"linkRel(link)\"\n [attr.aria-current]=\"link.current ? 'page' : null\"\n >\n {{ link.label }}\n @if (link.external) {\n <span class=\"footer__sr-only\">{{ externalLinkHint() }}</span>\n <lc-icon\n name=\"external-link\"\n size=\"xs\"\n class=\"footer__external-icon\"\n aria-hidden=\"true\"\n [decorative]=\"true\"\n />\n }\n </a>\n </li>\n }\n </ul>\n </div>\n }\n </nav>\n }\n <div class=\"footer__bottom\">\n <ng-content />\n @if (copyright()) {\n <span class=\"footer__copyright\">{{ copyright() }}</span>\n }\n </div>\n</footer>\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-footer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/footer/footer.component.ts","../../../../libs/angular-ui-kit/src/lib/footer/footer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_EXTERNAL_LINK } from '@lifecorner/angular-ui-kit/icons';\n\nexport interface FooterLink {\n /** Display label */\n label: string;\n /** URL or route */\n href: string;\n /**\n * Opens in a new tab (`target=\"_blank\"`). Screen readers hear the\n * `externalLinkHint` after the label; `rel` defaults to\n * `noopener noreferrer`.\n */\n external?: boolean;\n /** Marks the page the visitor is on (`aria-current=\"page\"`). */\n current?: boolean;\n /**\n * `rel` attribute, e.g. `me` for your own social profiles. Replaces the\n * `noopener noreferrer` default of external links.\n */\n rel?: string;\n}\n\nexport interface FooterSection {\n /** Section heading */\n title: string;\n /** Links in this section */\n links: FooterLink[];\n}\n\nexport type FooterVariant = 'default' | 'primary' | 'dark' | 'neutral';\n\n/** Heading level of the section titles (`headingLevel`). */\nexport type FooterHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\n@Component({\n selector: 'lc-footer',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_EXTERNAL_LINK])],\n templateUrl: './footer.component.html',\n styleUrls: ['./footer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Footer component for application-wide bottom navigation.\n *\n * Features:\n * - Multi-section navigation with link groups, inside a labelled `<nav>`\n * (`navAriaLabel`)\n * - Section titles at a configurable heading level (`headingLevel`, default 4)\n * - Links: `current` page (`aria-current`), `rel`, `external` (new tab, with a\n * text hint for screen readers — `externalLinkHint`); 44 px touch targets\n * - Copyright text display\n * - Compact single-row layout option\n * - Color variants (default, primary, dark, neutral)\n * - Optional top border\n *\n * @example\n * ```html\n * <lc-footer [sections]=\"navSections\" copyright=\"© 2026 Company\" />\n * ```\n */\nexport class FooterComponent {\n /** Footer navigation sections */\n sections = input<FooterSection[]>([]);\n\n /** Copyright text */\n copyright = input<string>();\n\n /** Whether to show a top border */\n showBorder = input<boolean>(true);\n\n /** Compact layout (single row) */\n compact = input<boolean>(false);\n\n /** Color variant */\n variant = input<FooterVariant>('default');\n\n /**\n * Heading level of the section titles. Pick the level that continues the\n * page's outline (a page with `h1` + `h2` sections usually wants `2`).\n */\n headingLevel = input<FooterHeadingLevel>(4);\n\n /** Accessible name of the `<nav>` around the sections. */\n navAriaLabel = input<string>('Footer');\n\n /** Visually hidden text read after the label of an `external` link. */\n externalLinkHint = input<string>('(opens in a new tab)');\n\n protected footerClasses = computed(() => {\n const classes = ['footer', `footer--${this.variant()}`];\n if (this.showBorder()) classes.push('footer--bordered');\n if (this.compact()) classes.push('footer--compact');\n return classes.join(' ');\n });\n\n /** `rel` of a link: its own value, else `noopener noreferrer` for external links. */\n protected linkRel(link: FooterLink): string | null {\n return link.rel ?? (link.external ? 'noopener noreferrer' : null);\n }\n\n protected currentYear = new Date().getFullYear();\n}\n","<footer [class]=\"footerClasses()\" role=\"contentinfo\">\n @if (sections().length > 0 && !compact()) {\n <nav class=\"footer__sections\" [attr.aria-label]=\"navAriaLabel()\">\n @for (section of sections(); track section.title) {\n <div class=\"footer__section\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"footer__section-title\">{{ section.title }}</h2>\n }\n @case (3) {\n <h3 class=\"footer__section-title\">{{ section.title }}</h3>\n }\n @case (5) {\n <h5 class=\"footer__section-title\">{{ section.title }}</h5>\n }\n @case (6) {\n <h6 class=\"footer__section-title\">{{ section.title }}</h6>\n }\n @default {\n <h4 class=\"footer__section-title\">{{ section.title }}</h4>\n }\n }\n <ul class=\"footer__links\">\n @for (link of section.links; track link.href) {\n <li>\n <a\n class=\"footer__link\"\n [class.footer__link--current]=\"link.current\"\n [href]=\"link.href\"\n [target]=\"link.external ? '_blank' : '_self'\"\n [attr.rel]=\"linkRel(link)\"\n [attr.aria-current]=\"link.current ? 'page' : null\"\n >\n {{ link.label }}\n @if (link.external) {\n <span class=\"footer__sr-only\">{{ externalLinkHint() }}</span>\n <lc-icon\n name=\"external-link\"\n size=\"xs\"\n class=\"footer__external-icon\"\n aria-hidden=\"true\"\n [decorative]=\"true\"\n />\n }\n </a>\n </li>\n }\n </ul>\n </div>\n }\n </nav>\n }\n <div class=\"footer__bottom\">\n <ng-content />\n @if (copyright()) {\n <span class=\"footer__copyright\">{{ copyright() }}</span>\n }\n </div>\n</footer>\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, HostListener, Injector, afterNextRender, computed, inject, input, output, signal, viewChild } from "@angular/core";
|
|
3
3
|
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
|
4
|
-
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
4
|
+
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
5
|
+
import { ICON_CHEVRON_LEFT, ICON_CHEVRON_RIGHT, ICON_PLUS, ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
|
|
5
6
|
import { NgStyle } from "@angular/common";
|
|
6
7
|
import { isSafeImageUrl } from "@lifecorner/angular-ui-kit/internal/url";
|
|
7
8
|
const SIZE_COLUMNS = {
|
|
@@ -371,6 +372,12 @@ var GalleryComponent = class GalleryComponent {
|
|
|
371
372
|
exportAs: ["cdkTrapFocus"]
|
|
372
373
|
}
|
|
373
374
|
],
|
|
375
|
+
viewProviders: [ɵprovideKitIcons([
|
|
376
|
+
ICON_PLUS,
|
|
377
|
+
ICON_X_MARK,
|
|
378
|
+
ICON_CHEVRON_LEFT,
|
|
379
|
+
ICON_CHEVRON_RIGHT
|
|
380
|
+
])],
|
|
374
381
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
375
382
|
});
|
|
376
383
|
};
|
|
@@ -389,6 +396,12 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
389
396
|
NgStyle,
|
|
390
397
|
CdkTrapFocus
|
|
391
398
|
],
|
|
399
|
+
viewProviders: [ɵprovideKitIcons([
|
|
400
|
+
ICON_PLUS,
|
|
401
|
+
ICON_X_MARK,
|
|
402
|
+
ICON_CHEVRON_LEFT,
|
|
403
|
+
ICON_CHEVRON_RIGHT
|
|
404
|
+
])],
|
|
392
405
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
393
406
|
host: { "[class]": "\"gallery\"" },
|
|
394
407
|
template: "<!-- Filters -->\n@if (showFilters()) {\n <div class=\"gallery__filters\">\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === null\"\n (click)=\"setFilter(null)\"\n >\n {{ filterAllLabel() }}\n </button>\n @for (cat of categories(); track cat) {\n <button\n type=\"button\"\n class=\"gallery__filter-btn\"\n [class.gallery__filter-btn--active]=\"activeFilter() === cat\"\n (click)=\"setFilter(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n}\n\n<!-- Grid / Masonry -->\n<div class=\"gallery__grid\" [class.gallery__grid--masonry]=\"layout() === 'masonry'\" [ngStyle]=\"gridStyle()\">\n @for (item of filteredItems(); track item.src; let i = $index) {\n <div\n class=\"gallery__item\"\n [class.gallery__item--loaded]=\"isLoaded(i)\"\n [class.gallery__item--clickable]=\"enableLightbox()\"\n [ngStyle]=\"itemStyle()\"\n (click)=\"openLightbox(i, $event)\"\n (keydown.enter)=\"openLightbox(i, $event)\"\n (keydown.space)=\"$event.preventDefault()\"\n (keyup.space)=\"openLightbox(i, $event)\"\n [attr.tabindex]=\"enableLightbox() ? 0 : null\"\n [attr.role]=\"enableLightbox() ? 'button' : null\"\n [attr.aria-label]=\"item.alt || imageFallbackLabel() + ' ' + (i + 1)\"\n >\n @if (!isLoaded(i)) {\n <div class=\"gallery__placeholder\"></div>\n }\n <img\n class=\"gallery__thumb\"\n [src]=\"getThumbnail(item)\"\n [alt]=\"item.alt || ''\"\n loading=\"lazy\"\n (load)=\"onImageLoad(i)\"\n />\n @if (showCaptions() && item.caption) {\n <div class=\"gallery__caption\">{{ item.caption }}</div>\n }\n </div>\n }\n</div>\n\n<!-- Lightbox overlay -->\n@if (lightboxItem(); as item) {\n <div\n class=\"gallery__lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"lightboxAriaLabel()\"\n tabindex=\"-1\"\n cdkTrapFocus\n (keydown)=\"onKeydown($event)\"\n >\n <!-- Backdrop -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- backdrop click is a mouse shortcut; Escape and the close button close the lightbox -->\n <div class=\"gallery__lightbox-backdrop\" (click)=\"closeLightbox()\"></div>\n\n <!-- Toolbar -->\n <div class=\"gallery__lightbox-toolbar\">\n <div class=\"gallery__lightbox-counter\">{{ lightboxCounter() }}</div>\n <div class=\"gallery__lightbox-actions\">\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomOutAriaLabel()\" (click)=\"zoomOut()\">\n <lc-icon name=\"minus\" size=\"sm\" />\n </button>\n <span class=\"gallery__lightbox-zoom\">{{ lightboxZoom() }}%</span>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"zoomInAriaLabel()\" (click)=\"zoomIn()\">\n <lc-icon name=\"plus\" size=\"sm\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"resetZoomAriaLabel()\" (click)=\"resetZoom()\">\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" />\n </button>\n <button type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"downloadAriaLabel()\" (click)=\"downloadCurrent()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" />\n </button>\n <button #closeBtn type=\"button\" class=\"gallery__lightbox-btn\" [attr.aria-label]=\"closeAriaLabel()\" (click)=\"closeLightbox()\">\n <lc-icon name=\"x-mark\" size=\"sm\" />\n </button>\n </div>\n </div>\n\n <!-- Navigation -->\n @if (canGoPrev()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--prev\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPrev()\"\n >\n <lc-icon name=\"chevron-left\" size=\"lg\" />\n </button>\n }\n @if (canGoNext()) {\n <button\n type=\"button\"\n class=\"gallery__lightbox-nav gallery__lightbox-nav--next\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNext()\"\n >\n <lc-icon name=\"chevron-right\" size=\"lg\" />\n </button>\n }\n\n <!-- Image -->\n <div class=\"gallery__lightbox-content\">\n <img\n class=\"gallery__lightbox-image\"\n [src]=\"item.src\"\n [alt]=\"item.alt || ''\"\n [style.transform]=\"lightboxTransform()\"\n />\n </div>\n\n <!-- Caption -->\n @if (item.caption) {\n <div class=\"gallery__lightbox-caption\">{{ item.caption }}</div>\n }\n </div>\n}\n",
|