@libs-ui/components-gallery
v0.2.357-10
Published
> Component hiển thị gallery ảnh với lightbox viewer, hỗ trợ zoom, drag, fullscreen và keyboard navigation.
Downloads
4,056
Readme
@libs-ui/components-gallery
Component hiển thị gallery ảnh với lightbox viewer, hỗ trợ zoom, drag, fullscreen và keyboard navigation.
Giới thiệu
@libs-ui/components-gallery cung cấp component gallery ảnh dạng thumbnail grid, khi click vào thumbnail sẽ mở lightbox viewer fullscreen. Viewer hỗ trợ zoom in/out, kéo thả ảnh đã phóng to, chuyển ảnh bằng phím mũi tên, và chế độ fullscreen. Component cũng hỗ trợ nhận data dạng Array<string> và tự động convert sang định dạng nội bộ.
Tính năng
- ✅ Thumbnail Grid: Hiển thị danh sách ảnh dạng thumbnail, hỗ trợ cắt slice (
start/end) - ✅ Lightbox Viewer: Viewer fullscreen được inject động vào
document.body - ✅ Zoom & Drag: Zoom in/out và kéo thả ảnh khi đã phóng to vượt khung
- ✅ Fullscreen Mode: Mở rộng viewer chiếm toàn màn hình
- ✅ Keyboard Navigation:
Escapeđóng viewer,ArrowLeft/ArrowRightchuyển ảnh - ✅ String Array Support: Tự động convert
Array<string>sang object format quaimageArrayStringConvert - ✅ Overlay Count: Hiển thị overlay "+N" trên thumbnail cuối khi ảnh nhiều hơn
end - ✅ FunctionsControl: Mở viewer từ bên ngoài component qua
outFunctionsControl - ✅ Error Fallback: Hiển thị icon placeholder khi ảnh load lỗi
- ✅ Embed Frame Support: Gửi postMessage tới parent khi chạy trong iframe
Khi nào sử dụng
- Khi cần hiển thị danh sách ảnh dạng thumbnail grid trong card, form, hoặc detail page
- Khi cần lightbox viewer với zoom, drag và fullscreen cho ảnh tải lên từ người dùng
- Khi data ảnh là
Array<string>(mảng URL thuần) thay vì array object - Khi cần điều khiển mở viewer từ bên ngoài component (ví dụ: click nút "Xem ảnh")
- Khi cần giới hạn số thumbnail hiển thị và cho biết có bao nhiêu ảnh còn lại (+N)
Cài đặt
npm install @libs-ui/components-galleryImport
import {
LibsUiComponentsGalleryComponent,
LibsUiComponentsGalleryViewerComponent,
IGalleryFunctionsControlEvent,
IZoomDragHTMLElement,
} from '@libs-ui/components-gallery';Ví dụ sử dụng
1. Gallery cơ bản
import { Component } from '@angular/core';
import { LibsUiComponentsGalleryComponent } from '@libs-ui/components-gallery';
@Component({
selector: 'app-product-images',
standalone: true,
imports: [LibsUiComponentsGalleryComponent],
template: `
<div class="w-[300px] h-[80px]">
<libs_ui-components-gallery
[images]="productImages"
[fieldDisplaySrcImage]="'url'"
/>
</div>
`,
})
export class ProductImagesComponent {
productImages = [
{ url: 'https://example.com/product1.jpg' },
{ url: 'https://example.com/product2.jpg' },
{ url: 'https://example.com/product3.jpg' },
{ url: 'https://example.com/product4.jpg' },
{ url: 'https://example.com/product5.jpg' },
];
}2. Gallery từ Array<string>
Khi data trả về từ API là mảng URL thuần, dùng imageArrayStringConvert để convert tự động:
import { Component } from '@angular/core';
import { LibsUiComponentsGalleryComponent } from '@libs-ui/components-gallery';
@Component({
selector: 'app-attachment-gallery',
standalone: true,
imports: [LibsUiComponentsGalleryComponent],
template: `
<div class="w-[300px] h-[80px]">
<libs_ui-components-gallery
[images]="[]"
[fieldDisplaySrcImage]="'src'"
[imageArrayStringConvert]="attachmentUrls"
/>
</div>
`,
})
export class AttachmentGalleryComponent {
attachmentUrls = [
'https://example.com/attach1.jpg',
'https://example.com/attach2.jpg',
'https://example.com/attach3.jpg',
];
}3. Gallery với custom range và ẩn overlay count
import { Component } from '@angular/core';
import { LibsUiComponentsGalleryComponent } from '@libs-ui/components-gallery';
@Component({
selector: 'app-preview-gallery',
standalone: true,
imports: [LibsUiComponentsGalleryComponent],
template: `
<div class="w-[400px] h-[80px]">
<libs_ui-components-gallery
[images]="photos"
[fieldDisplaySrcImage]="'photoUrl'"
[start]="0"
[end]="4"
[ignoreOverlayCountImage]="true"
/>
</div>
`,
})
export class PreviewGalleryComponent {
photos = [
{ photoUrl: 'https://example.com/photo1.jpg', name: 'Photo 1' },
{ photoUrl: 'https://example.com/photo2.jpg', name: 'Photo 2' },
{ photoUrl: 'https://example.com/photo3.jpg', name: 'Photo 3' },
{ photoUrl: 'https://example.com/photo4.jpg', name: 'Photo 4' },
];
}4. Mở viewer từ bên ngoài (FunctionsControl)
import { Component, signal } from '@angular/core';
import {
LibsUiComponentsGalleryComponent,
IGalleryFunctionsControlEvent,
} from '@libs-ui/components-gallery';
@Component({
selector: 'app-order-detail',
standalone: true,
imports: [LibsUiComponentsGalleryComponent],
template: `
<div class="w-[300px] h-[80px]">
<libs_ui-components-gallery
[images]="orderImages"
[fieldDisplaySrcImage]="'url'"
(outFunctionsControl)="handlerFunctionsControl($event)"
(outViewerEvent)="handlerViewerEvent($event)"
/>
</div>
<button
class="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
(click)="handlerOpenViewer($event)">
Xem ảnh đầu tiên
</button>
<span class="ml-2 text-sm text-gray-500">
Viewer: {{ viewerStatus() ?? 'đóng' }}
</span>
`,
})
export class OrderDetailComponent {
protected galleryControls = signal<IGalleryFunctionsControlEvent | null>(null);
protected viewerStatus = signal<'show' | 'remove' | null>(null);
orderImages = [
{ url: 'https://example.com/order1.jpg' },
{ url: 'https://example.com/order2.jpg' },
{ url: 'https://example.com/order3.jpg' },
];
protected handlerFunctionsControl(event: IGalleryFunctionsControlEvent): void {
event.stopPropagation?.();
this.galleryControls.set(event);
}
protected handlerViewerEvent(event: 'show' | 'remove'): void {
this.viewerStatus.set(event);
}
protected handlerOpenViewer(event: Event): void {
event.stopPropagation();
this.galleryControls()?.open(this.orderImages[0]);
}
}5. Single image (1 ảnh, viewer không có thumbnail strip)
import { Component } from '@angular/core';
import { LibsUiComponentsGalleryComponent } from '@libs-ui/components-gallery';
@Component({
selector: 'app-avatar-preview',
standalone: true,
imports: [LibsUiComponentsGalleryComponent],
template: `
<div class="w-[100px] h-[80px]">
<libs_ui-components-gallery
[images]="[]"
[fieldDisplaySrcImage]="'url'"
[imageArrayStringConvert]="avatarUrl"
/>
</div>
`,
})
export class AvatarPreviewComponent {
avatarUrl = ['https://example.com/avatar.jpg'];
}@Input() — libs_ui-components-gallery
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [(images)] | Array<Record<string, any>> | required | Danh sách ảnh (model — two-way binding). Component có thể ghi lại khi dùng imageArrayStringConvert | [images]="photoList" |
| [end] | number | 3 | Index kết thúc khi slice mảng ảnh để hiển thị thumbnails | [end]="5" |
| [fieldDisplaySrcImage] | string | 'url' | Tên key trong object chứa URL ảnh | [fieldDisplaySrcImage]="'photoUrl'" |
| [ignoreOverlayCountImage] | boolean | undefined | Ẩn overlay đếm số ảnh còn lại (+N) trên thumbnail cuối | [ignoreOverlayCountImage]="true" |
| [imageArrayStringConvert] | Array<string> | undefined | Mảng URL dạng string — tự động convert sang images format theo fieldDisplaySrcImage | [imageArrayStringConvert]="urlList" |
| [start] | number | 0 | Index bắt đầu khi slice mảng ảnh để hiển thị thumbnails | [start]="0" |
| [zIndex] | number | 1200 | z-index của viewer overlay khi mở | [zIndex]="1500" |
@Output() — libs_ui-components-gallery
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outFunctionsControl) | IGalleryFunctionsControlEvent | Emit ngay khi init và mỗi lần viewer mở/đóng — cung cấp method open() để điều khiển viewer từ bên ngoài | handlerFunctionsControl(event: IGalleryFunctionsControlEvent): void { event.stopPropagation?.(); this.controls.set(event); } | (outFunctionsControl)="handlerFunctionsControl($event)" |
| (outViewerEvent) | 'show' \| 'remove' | Emit 'show' khi viewer mở, 'remove' khi viewer đóng | handlerViewerEvent(event: 'show' \| 'remove'): void { this.viewerStatus.set(event); } | (outViewerEvent)="handlerViewerEvent($event)" |
Sub-component: Viewer
libs_ui-components-gallery-viewer là viewer fullscreen được tạo động và inject vào document.body. Thông thường không cần dùng trực tiếp — gallery component quản lý tự động.
@Input() — libs_ui-components-gallery-viewer
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [classContainerInclude] | string | undefined | CSS class bổ sung cho container viewer | [classContainerInclude]="'custom-viewer'" |
| [fieldDisplaySrcImage] | string | required | Key trong object chứa URL ảnh | [fieldDisplaySrcImage]="'url'" |
| [(imageSelected)] | Record<string, any> | undefined | Ảnh đang được chọn hiển thị (model — two-way binding) | [(imageSelected)]="currentImage" |
| [images] | Array<Record<string, any>> | required | Danh sách tất cả ảnh để hiển thị trong thumbnail strip | [images]="allImages" |
| [removeBackdrop] | boolean | undefined | Ẩn backdrop overlay phía sau viewer | [removeBackdrop]="true" |
| [singleImage] | boolean | false | Chế độ 1 ảnh — ẩn thumbnail strip, hiển thị ảnh đầu tiên trực tiếp | [singleImage]="true" |
| [zIndex] | number | 1200 | z-index của viewer | [zIndex]="1500" |
@Output() — libs_ui-components-gallery-viewer
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outClose) | void | Emit khi người dùng đóng viewer (click nút đóng, nhấn Escape) | handlerCloseViewer(event: Event): void { event.stopPropagation(); this.viewerRef?.destroy(); } | (outClose)="handlerCloseViewer($event)" |
FunctionsControl API
outFunctionsControl emit một object IGalleryFunctionsControlEvent — dùng để điều khiển gallery từ code bên ngoài:
| Property | Kiểu | Mô tả |
|---|---|---|
| open(imageSelected) | (imageSelected: Record<string, any>) => Promise<void> | Mở viewer và hiển thị ảnh được truyền vào. imageSelected phải là object có cùng key fieldDisplaySrcImage với ảnh trong mảng images |
| viewerRef | ComponentRef<LibsUiComponentsGalleryViewerComponent> \| undefined | Tham chiếu đến ComponentRef của viewer đang mở. undefined khi viewer chưa mở |
// Lấy controls qua outFunctionsControl
protected galleryControls = signal<IGalleryFunctionsControlEvent | null>(null);
// Mở viewer với ảnh cụ thể
protected handlerOpenFirstImage(event: Event): void {
event.stopPropagation();
const firstImage = this.images[0];
this.galleryControls()?.open(firstImage);
}
// Kiểm tra viewer có đang mở không
protected isViewerOpen(): boolean {
return !!this.galleryControls()?.viewerRef;
}Types & Interfaces
import {
IGalleryFunctionsControlEvent,
IZoomDragHTMLElement,
} from '@libs-ui/components-gallery';
// Interface cho FunctionsControl (emit qua outFunctionsControl)
export interface IGalleryFunctionsControlEvent {
open: (imageSelected: Record<string, any>) => Promise<void>;
viewerRef: ComponentRef<any> | undefined;
}
// Interface nội bộ cho element hỗ trợ drag khi zoom (internal — hiếm khi cần dùng trực tiếp)
export interface IZoomDragHTMLElement extends HTMLElement {
stopDraggableBehavior?(): IZoomDragHTMLElement | null;
}Lưu ý quan trọng
⚠️ [(images)] là model (two-way binding): Khi dùng imageArrayStringConvert, component sẽ tự động ghi lại vào images. Truyền [images]="[]" khi chỉ dùng imageArrayStringConvert.
⚠️ imageArrayStringConvert convert theo fieldDisplaySrcImage: Mảng ['url1.jpg', 'url2.jpg'] sẽ được convert thành [{ url: 'url1.jpg' }, { url: 'url2.jpg' }] nếu fieldDisplaySrcImage = 'url'. Hai input này phải nhất quán với nhau.
⚠️ Viewer inject vào document.body: Viewer là dynamic component không nằm trong DOM tree của gallery component. z-index mặc định là 1200 — tăng lên nếu có modal/drawer bao bên ngoài có z-index cao hơn.
⚠️ Keyboard navigation trong viewer: Escape đóng viewer, ArrowLeft chuyển ảnh trước, ArrowRight chuyển ảnh tiếp. Event được lắng nghe từ document — hoạt động bất kể focus đang ở đâu.
⚠️ Overlay "+N" hiển thị trên thumbnail cuối: Khi images.length > end, thumbnail cuối hiển thị overlay +N (N = tổng ảnh - end). Viewer vẫn hiển thị toàn bộ ảnh. Tắt bằng [ignoreOverlayCountImage]="true".
⚠️ outFunctionsControl emit nhiều lần: Event này emit khi ngOnInit, khi viewer mở và khi viewer đóng — không chỉ emit một lần. Luôn dùng giá trị mới nhất từ signal.
Test
npx nx test components-gallery