@libs-ui/components-popover
v0.2.357-11
Published
> Component/Directive hiển thị nội dung nổi (popover/tooltip) gắn liền với phần tử gốc, tự động định vị thông minh, hỗ trợ nhiều chế độ kích hoạt và nội dung tùy chỉnh.
Downloads
3,369
Readme
@libs-ui/components-popover
Component/Directive hiển thị nội dung nổi (popover/tooltip) gắn liền với phần tử gốc, tự động định vị thông minh, hỗ trợ nhiều chế độ kích hoạt và nội dung tùy chỉnh.
Giới thiệu
@libs-ui/components-popover cung cấp giải pháp hiển thị thông tin ngữ cảnh linh hoạt trong ứng dụng Angular. Lib hỗ trợ cả hai cách dùng: dạng Directive gắn trực tiếp vào phần tử HTML bất kỳ, hoặc dạng Component Selector để bọc nội dung phức tạp. Popover tự động tính toán vị trí tối ưu dựa trên viewport, fallback sang hướng khác nếu không đủ chỗ, và tự dọn dẹp khi component bị destroy.
Tính năng
- ✅ Hai cách dùng: Directive (
[LibsUiComponentsPopoverDirective]) và Component Selector (libs_ui-components-popover) - ✅ Tự động định vị thông minh: Tính toán hướng tốt nhất dựa trên viewport, tự fallback nếu bị tràn
- ✅ Nhiều chế độ kích hoạt:
hover,click,click-toggle,click_open_and_click_panel_content_hidden - ✅ Hỗ trợ
ng-template: Truyền nội dung phức tạp quaTemplateRef - ✅ Chế độ text-ellipsis: Chỉ hiển thị khi text bị cắt ngắn (
type="text") - ✅ FunctionControl API: Điều khiển popover từ bên ngoài qua
outFunctionsControl - ✅ Hỗ trợ iframe: Render popover vào parent document khi chạy trong iframe
- ✅ Tự cleanup: Tự dọn dẹp overlay khi component bị destroy, không memory leak
- ✅ OnPush + Angular Signals
Khi nào sử dụng
- Hiển thị tooltip thông tin bổ sung khi di chuột qua icon hoặc văn bản bị truncate
- Hiển thị dropdown menu hoặc panel tùy chọn khi nhấn vào nút
- Hiển thị card chi tiết (user profile, product info) khi hover
- Xây dựng các component dropdown tùy chỉnh cần định vị thông minh
- Thông báo hướng dẫn, hint tại chỗ cho người dùng
Cài đặt
npm install @libs-ui/components-popoverImport
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
// ...
})
export class YourComponent {}Lưu ý:
LibsUiComponentsPopoverComponentbao gồm cả selector dạng Directive ([LibsUiComponentsPopoverDirective]). Chỉ cần import một lần cho cả hai cách dùng.
Ví dụ sử dụng
Ví dụ 1 — Dạng Directive (Tooltip đơn giản)
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
template: `
<span
LibsUiComponentsPopoverDirective
[config]="{ content: 'Tên đầy đủ: Nguyễn Văn A', direction: 'bottom' }">
Nguyễn V. A
</span>
`,
})
export class BasicTooltipComponent {}Ví dụ 2 — Dạng Component Selector với nội dung phức tạp
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
template: `
<libs_ui-components-popover
[config]="popoverConfig"
[mode]="'click-toggle'"
(outEvent)="handlerPopoverEvent($event)"
(outFunctionsControl)="handlerFunctionsControl($event)">
<button class="px-4 py-2 bg-blue-600 text-white rounded">
Mở menu
</button>
</libs_ui-components-popover>
`,
})
export class ClickPopoverComponent {
protected popoverConfig = {
content: 'Nội dung menu',
direction: 'bottom' as const,
maxWidth: 200,
};
private popoverControl?: IPopoverFunctionControlEvent;
handlerFunctionsControl(control: IPopoverFunctionControlEvent): void {
event?.stopPropagation();
this.popoverControl = control;
}
handlerPopoverEvent(event: string): void {
event?.stopPropagation();
console.log('Popover event:', event);
}
closePopover(): void {
this.popoverControl?.removePopoverOverlay();
}
}Ví dụ 3 — Dùng ng-template cho nội dung tùy chỉnh
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
template: `
<div
LibsUiComponentsPopoverDirective
[config]="{ template: profileTpl, maxWidth: 280, direction: 'right' }">
<img src="avatar.png" alt="Avatar" class="w-10 h-10 rounded-full cursor-pointer" />
</div>
<ng-template #profileTpl>
<div class="p-4">
<div class="font-bold text-gray-800">Nguyễn Văn A</div>
<div class="text-sm text-gray-500">[email protected]</div>
<hr class="my-3" />
<button class="text-sm text-red-500">Đăng xuất</button>
</div>
</ng-template>
`,
})
export class ProfilePopoverComponent {}Ví dụ 4 — Chế độ text-ellipsis (chỉ hiện khi text bị cắt)
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
template: `
<div
LibsUiComponentsPopoverDirective
[type]="'text'"
[config]="{ content: 'Đây là đoạn văn bản dài sẽ bị cắt ngắn khi container quá nhỏ', direction: 'top' }"
style="width: 120px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
Đây là đoạn văn bản dài sẽ bị cắt ngắn khi container quá nhỏ
</div>
`,
})
export class TextEllipsisPopoverComponent {}Ví dụ 5 — Điều khiển từ bên ngoài (FunctionControl)
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
@Component({
standalone: true,
imports: [LibsUiComponentsPopoverComponent],
template: `
<libs_ui-components-popover
[config]="{ content: 'Popover được mở từ bên ngoài', direction: 'bottom' }"
[mode]="'click'"
[ignoreShowPopover]="true"
(outFunctionsControl)="handlerFunctionsControl($event)">
<span>Phần tử target</span>
</libs_ui-components-popover>
<button (click)="handlerOpenPopover($event)">Mở Popover từ nút khác</button>
<button (click)="handlerClosePopover($event)">Đóng Popover</button>
`,
})
export class ControlledPopoverComponent {
private popoverControl?: IPopoverFunctionControlEvent;
handlerFunctionsControl(control: IPopoverFunctionControlEvent): void {
event?.stopPropagation();
this.popoverControl = control;
}
handlerOpenPopover(event: MouseEvent): void {
event.stopPropagation();
this.popoverControl?.showPopover();
}
handlerClosePopover(event: MouseEvent): void {
event.stopPropagation();
this.popoverControl?.removePopoverOverlay();
}
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [config] | IPopoverOverlay | {} | Cấu hình nội dung, hướng, kích thước và style của popover. Bắt buộc truyền để popover hiển thị. | [config]="{ content: 'Hello', direction: 'top' }" |
| [mode] | TYPE_POPOVER_MODE | 'hover' | Chế độ kích hoạt hiển thị popover. Xem bảng TYPE_POPOVER_MODE bên dưới. | [mode]="'click-toggle'" |
| [type] | 'text' \| 'other' | 'other' | Khi là 'text', popover chỉ hiển thị khi nội dung bị truncate (clientWidth >= scrollWidth). | [type]="'text'" |
| [ignoreShowPopover] | boolean | undefined | Khi true, vô hiệu hóa hoàn toàn việc hiển thị popover. Hữu ích khi muốn kiểm soát thủ công qua FunctionControl. | [ignoreShowPopover]="isDisabled()" |
| [elementRefCustom] | HTMLElement | undefined | Element tùy chỉnh để gắn popover vào thay vì host element mặc định. | [elementRefCustom]="myElementRef" |
| [initEventInElementRefCustom] | boolean | false | Khi true, gắn event listener vào elementRefCustom thay vì host element. Phải dùng kèm elementRefCustom. | [initEventInElementRefCustom]="true" |
| [classInclude] | string | '' | Các CSS class bổ sung sẽ được thêm vào host element. | [classInclude]="'cursor-pointer text-blue-500'" |
| [flagMouse] | IFlagMouse | { isMouseEnter: false, isMouseEnterContent: false, isContainerHasScroll: false } | Trạng thái chuột từ component cha, dùng để ngăn đóng popover khi chuột còn trong vùng liên quan. | [flagMouse]="parentFlagMouse()" |
| [ignoreHiddenPopoverContentWhenMouseLeave] | boolean | undefined | Khi true, không ẩn popover khi chuột rời khỏi content overlay. | [ignoreHiddenPopoverContentWhenMouseLeave]="true" |
| [ignoreClickOutside] | boolean | undefined | Khi true, không đóng popover khi click bên ngoài vùng popover. | [ignoreClickOutside]="true" |
| [ignoreStopPropagationEvent] | boolean | undefined | Khi true, không gọi stopPropagation trên các event — cho phép event lan truyền lên component cha. | [ignoreStopPropagationEvent]="true" |
| [ignoreCursorPointerModeLikeClick] | boolean | undefined | Khi true, không thêm class cursor-pointer vào host element khi mode là click. | [ignoreCursorPointerModeLikeClick]="true" |
| [isAddContentToParentDocument] | boolean | undefined | Khi true, render overlay vào parent document. Dùng khi popover nằm trong iframe. | [isAddContentToParentDocument]="true" |
| [debugId] | string | undefined | ID dùng cho mục đích debug. | [debugId]="'user-avatar-popover'" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outEvent) | TYPE_POPOVER_EVENT | Phát ra khi popover thay đổi trạng thái: 'show' khi mở, 'remove' khi đóng, 'click' khi click vào host, 'mouseenter-element'/'mouseleave-element' khi chuột vào/ra khỏi host, 'mouseenter-content'/'mouseleave-content' khi chuột vào/ra khỏi overlay. | handlerEvent(event: TYPE_POPOVER_EVENT): void { event?.stopPropagation(); ... } | (outEvent)="handlerEvent($event)" |
| (outChangStageFlagMouse) | IFlagMouse | Phát ra mỗi khi trạng thái chuột thay đổi. Truyền kết quả vào [flagMouse] của popover khác để đồng bộ nhiều popover liên quan. | handlerFlagMouseChange(flag: IFlagMouse): void { event?.stopPropagation(); this.flagMouse.set(flag); } | (outChangStageFlagMouse)="handlerFlagMouseChange($event)" |
| (outEventPopoverContent) | TYPE_POPOVER_DIRECTION | Phát ra hướng thực tế mà popover đang hiển thị sau khi tính toán. Hữu ích để điều chỉnh UI dựa trên vị trí overlay. | handlerDirectionChange(dir: TYPE_POPOVER_DIRECTION): void { event?.stopPropagation(); this.currentDirection.set(dir); } | (outEventPopoverContent)="handlerDirectionChange($event)" |
| (outFunctionsControl) | IPopoverFunctionControlEvent | Phát ra object chứa các hàm điều khiển popover từ bên ngoài. Nhận về ngay khi component khởi tạo. | handlerFunctionsControl(ctrl: IPopoverFunctionControlEvent): void { event?.stopPropagation(); this.popoverCtrl = ctrl; } | (outFunctionsControl)="handlerFunctionsControl($event)" |
Types & Interfaces
import {
IPopoverOverlay,
IPopoverFunctionControlEvent,
IFlagMouse,
IPopover,
TYPE_POPOVER_TYPE,
TYPE_POPOVER_MODE,
TYPE_POPOVER_DIRECTION,
TYPE_POPOVER_POSITION_MODE,
TYPE_POPOVER_EVENT,
} from '@libs-ui/components-popover';IPopoverOverlay — Cấu hình overlay
import { IPopoverOverlay } from '@libs-ui/components-popover';
export interface IPopoverOverlay {
// Nội dung
content?: string; // Nội dung dạng string HTML. Không dùng kèm template.
template?: TemplateRef<any>; // Nội dung dạng ng-template. Không dùng kèm content.
itemContext?: any; // Context truyền vào template khi dùng ng-template.
// Kích thước
width?: number; // Chiều rộng cố định (px)
minWidth?: number; // Chiều rộng tối thiểu (px)
maxWidth?: number; // Chiều rộng tối đa (px). Default: 250
maxHeight?: number | null; // Chiều cao tối đa (px). null = tự tính theo viewport. Default: 140
widthByParent?: boolean; // Nếu true, overlay rộng bằng phần tử cha
parentBorderWidth?: number; // Bù border của phần tử cha khi dùng widthByParent
// Vị trí & hướng
direction?: TYPE_POPOVER_DIRECTION; // Hướng ưu tiên. Default: 'bottom'
directionDistance?: number; // Khoảng cách bổ sung theo hướng (px)
position?: {
mode: TYPE_POPOVER_POSITION_MODE; // Căn chỉnh theo trục phụ: 'start' | 'center' | 'end'
distance: number; // Khoảng offset so với vị trí căn chỉnh (px)
autoUpdatePosition?: {
startDistance: number; // Offset khi mode tự chuyển sang 'start'
endDistance: number; // Offset khi mode tự chuyển sang 'end'
};
};
// Padding nội dung overlay
paddingTop?: number;
paddingRight?: number;
paddingBottom?: number;
paddingLeft?: number;
// Styling
classInclude?: string; // CSS class bổ sung cho wrapper overlay
classIncludeOverlayBody?: string; // CSS class bổ sung cho body overlay
whiteTheme?: boolean; // Dùng theme màu trắng
ngStyles?: Record<string, any>; // Inline styles bổ sung
// Mũi tên
ignoreArrow?: boolean; // Ẩn mũi tên chỉ hướng
arrowPositionCustom?: number; // Vị trí tùy chỉnh của mũi tên (px)
// Animation
animationConfig?: {
time?: number; // Thời gian animation (giây). Default: 0.4
distance?: number; // Khoảng cách trượt khi animate (px). Default: 12
};
// Hành vi
timerDestroy?: number; // Delay (ms) trước khi ẩn khi chuột rời. Default: 0
zIndex?: number; // z-index của overlay
isAddContentToParentDocument?: boolean; // Render vào parent document (dùng trong iframe)
scrollOverlayOptions?: IScrollOverlayOptions; // Tùy chọn scroll cho overlay
}TYPE_POPOVER_MODE — Chế độ kích hoạt
import { TYPE_POPOVER_MODE } from '@libs-ui/components-popover';
type TYPE_POPOVER_MODE =
| 'hover' // Hiển thị khi di chuột vào (mặc định)
| 'click' // Hiển thị khi click vào, không tự đóng
| 'click-toggle' // Click để mở, click lần nữa để đóng
| 'click_open_and_click_panel_content_hidden'; // Click để mở, click vào nội dung overlay để đóngTYPE_POPOVER_DIRECTION — Hướng hiển thị
import { TYPE_POPOVER_DIRECTION } from '@libs-ui/components-popover';
type TYPE_POPOVER_DIRECTION = 'top' | 'right' | 'bottom' | 'left';Khi hướng ưu tiên không đủ chỗ trong viewport, popover tự động thử theo thứ tự fallback:
bottom→bottom,top,right,lefttop→top,bottom,right,leftright→right,left,bottom,topleft→left,right,bottom,top
TYPE_POPOVER_POSITION_MODE — Căn chỉnh trục phụ
import { TYPE_POPOVER_POSITION_MODE } from '@libs-ui/components-popover';
type TYPE_POPOVER_POSITION_MODE = 'start' | 'center' | 'end';
// 'start' → Căn về phía bắt đầu của phần tử gốc (trái khi direction là top/bottom)
// 'center' → Căn giữa so với phần tử gốc (mặc định khi không chỉ định)
// 'end' → Căn về phía cuối của phần tử gốc (phải khi direction là top/bottom)TYPE_POPOVER_EVENT — Sự kiện
import { TYPE_POPOVER_EVENT } from '@libs-ui/components-popover';
type TYPE_POPOVER_EVENT =
| 'show' // Overlay vừa được hiển thị
| 'remove' // Overlay vừa bị xóa
| 'click' // Click vào host element
| 'mouseenter-element' // Chuột vào host element
| 'mouseleave-element' // Chuột rời khỏi host element
| 'mouseenter-content' // Chuột vào overlay content
| 'mouseleave-content'; // Chuột rời khỏi overlay contentIPopoverFunctionControlEvent — API điều khiển
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
interface IPopoverFunctionControlEvent {
showPopover: (elementRef?: HTMLElement) => Promise<void>; // Hiển thị popover (tùy chọn gắn vào element khác)
removePopoverOverlay: () => Promise<void>; // Đóng và xóa overlay
updatePopoverOverlayPosition: () => Promise<void>; // Tính toán lại vị trí overlay
updatePopoverOverlay: () => Promise<void>; // Cập nhật config và vị trí overlay
getRectContainer: () => Promise<IBoundingClientRect>; // Lấy vị trí phần tử gốc
getRectContent: () => Promise<IBoundingClientRect>; // Lấy vị trí overlay content
}IFlagMouse — Trạng thái chuột
import { IFlagMouse } from '@libs-ui/components-popover';
interface IFlagMouse {
isMouseEnter: boolean; // Chuột đang trong host element
isMouseEnterContent: boolean; // Chuột đang trong overlay content
isContainerHasScroll?: boolean; // Host element có scroll
}IPopover — Interface tổng hợp
import { IPopover } from '@libs-ui/components-popover';
interface IPopover {
type?: TYPE_POPOVER_TYPE;
mode?: TYPE_POPOVER_MODE;
dataView?: string;
config?: IPopoverOverlay;
classInclude?: string;
ignoreShowPopover?: boolean;
ignoreHiddenPopoverContentWhenMouseLeave?: boolean;
elementRefCustom?: HTMLElement;
isAddContentToParentDocument?: boolean;
templateCustomContent?: TemplateRef<any>;
}Lưu ý quan trọng
⚠️ Một import cho cả hai cách dùng: LibsUiComponentsPopoverComponent đăng ký cả selector dạng element (libs_ui-components-popover) lẫn attribute directive ([LibsUiComponentsPopoverDirective]). Chỉ cần import một lần vào imports[].
⚠️ Không truyền cả content và template cùng lúc: Nếu truyền cả hai, content sẽ bị bỏ qua. Chọn một trong hai cách truyền nội dung.
⚠️ maxHeight: null để tự động tính: Khi đặt maxHeight: null, chiều cao overlay sẽ tự điều chỉnh dựa trên khoảng trống còn lại trong viewport (tính từ top của host element xuống đáy màn hình trừ 44px).
⚠️ Chế độ hover với timerDestroy: Khi dùng nhiều popover lồng nhau (popover trong popover), sử dụng [flagMouse] và (outChangStageFlagMouse) để truyền trạng thái chuột giữa các popover, tránh đóng popover cha khi chuột đang trong popover con.
⚠️ Debug via URL query: Có thể đặt thời gian delay trước khi ẩn popover qua URL query ?popover-timer-destroy=500 (đơn vị ms) — hữu ích khi debug bằng DevTools.
⚠️ Iframe support: Khi component chạy trong iframe, dùng [isAddContentToParentDocument]="true" hoặc config.isAddContentToParentDocument = true để render overlay vào parent document và tránh bị cắt bởi overflow: hidden.
