@libs-ui/components-popover 0.2.356-9 → 0.2.357-1
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 +370 -78
- package/esm2022/interfaces/popover.interface.mjs +1 -1
- package/esm2022/overlay/overlay.component.mjs +1 -2
- package/esm2022/popover.component.mjs +7 -9
- package/fesm2022/libs-ui-components-popover.mjs +7 -10
- package/fesm2022/libs-ui-components-popover.mjs.map +1 -1
- package/interfaces/popover.interface.d.ts +2 -2
- package/overlay/overlay.component.d.ts +4 -4
- package/package.json +5 -5
- package/popover.component.d.ts +7 -8
package/README.md
CHANGED
|
@@ -1,139 +1,431 @@
|
|
|
1
1
|
# @libs-ui/components-popover
|
|
2
2
|
|
|
3
|
-
> Component hiển thị nội dung nổi (popover/tooltip) gắn liền với
|
|
3
|
+
> 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.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@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.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
- ✅ **
|
|
12
|
-
- ✅
|
|
13
|
-
- ✅ **
|
|
14
|
-
- ✅ **
|
|
15
|
-
- ✅ **
|
|
16
|
-
- ✅ **
|
|
17
|
-
- ✅ **
|
|
11
|
+
- ✅ **Hai cách dùng**: Directive (`[LibsUiComponentsPopoverDirective]`) và Component Selector (`libs_ui-components-popover`)
|
|
12
|
+
- ✅ **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
|
|
13
|
+
- ✅ **Nhiều chế độ kích hoạt**: `hover`, `click`, `click-toggle`, `click_open_and_click_panel_content_hidden`
|
|
14
|
+
- ✅ **Hỗ trợ `ng-template`**: Truyền nội dung phức tạp qua `TemplateRef`
|
|
15
|
+
- ✅ **Chế độ text-ellipsis**: Chỉ hiển thị khi text bị cắt ngắn (`type="text"`)
|
|
16
|
+
- ✅ **FunctionControl API**: Điều khiển popover từ bên ngoài qua `outFunctionsControl`
|
|
17
|
+
- ✅ **Hỗ trợ iframe**: Render popover vào parent document khi chạy trong iframe
|
|
18
|
+
- ✅ **Tự cleanup**: Tự dọn dẹp overlay khi component bị destroy, không memory leak
|
|
19
|
+
- ✅ **OnPush + Angular Signals**
|
|
18
20
|
|
|
19
21
|
## Khi nào sử dụng
|
|
20
22
|
|
|
21
|
-
- Hiển thị thông tin bổ sung
|
|
22
|
-
- Hiển thị menu hoặc
|
|
23
|
-
-
|
|
23
|
+
- Hiển thị tooltip thông tin bổ sung khi di chuột qua icon hoặc văn bản bị truncate
|
|
24
|
+
- Hiển thị dropdown menu hoặc panel tùy chọn khi nhấn vào nút
|
|
25
|
+
- Hiển thị card chi tiết (user profile, product info) khi hover
|
|
26
|
+
- Xây dựng các component dropdown tùy chỉnh cần định vị thông minh
|
|
27
|
+
- Thông báo hướng dẫn, hint tại chỗ cho người dùng
|
|
24
28
|
|
|
25
29
|
## Cài đặt
|
|
26
30
|
|
|
27
31
|
```bash
|
|
28
|
-
# npm
|
|
29
32
|
npm install @libs-ui/components-popover
|
|
30
|
-
|
|
31
|
-
# yarn
|
|
32
|
-
yarn add @libs-ui/components-popover
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Import
|
|
36
36
|
|
|
37
37
|
```typescript
|
|
38
|
-
import {
|
|
38
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
39
39
|
|
|
40
40
|
@Component({
|
|
41
41
|
standalone: true,
|
|
42
|
-
imports: [
|
|
42
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
43
43
|
// ...
|
|
44
44
|
})
|
|
45
45
|
export class YourComponent {}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
> **Lưu ý**: `LibsUiComponentsPopoverComponent` bao gồm cả selector dạng Directive (`[LibsUiComponentsPopoverDirective]`). Chỉ cần import một lần cho cả hai cách dùng.
|
|
49
|
+
|
|
50
|
+
## Ví dụ sử dụng
|
|
49
51
|
|
|
50
|
-
### Directive (
|
|
52
|
+
### Ví dụ 1 — Dạng Directive (Tooltip đơn giản)
|
|
51
53
|
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
```typescript
|
|
55
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
56
|
+
|
|
57
|
+
@Component({
|
|
58
|
+
standalone: true,
|
|
59
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
60
|
+
template: `
|
|
61
|
+
<span
|
|
62
|
+
LibsUiComponentsPopoverDirective
|
|
63
|
+
[config]="{ content: 'Tên đầy đủ: Nguyễn Văn A', direction: 'bottom' }">
|
|
64
|
+
Nguyễn V. A
|
|
65
|
+
</span>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class BasicTooltipComponent {}
|
|
58
69
|
```
|
|
59
70
|
|
|
60
|
-
### Component Selector
|
|
71
|
+
### Ví dụ 2 — Dạng Component Selector với nội dung phức tạp
|
|
61
72
|
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
75
|
+
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
|
|
76
|
+
|
|
77
|
+
@Component({
|
|
78
|
+
standalone: true,
|
|
79
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
80
|
+
template: `
|
|
81
|
+
<libs_ui-components-popover
|
|
82
|
+
[config]="popoverConfig"
|
|
83
|
+
[mode]="'click-toggle'"
|
|
84
|
+
(outEvent)="handlerPopoverEvent($event)"
|
|
85
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
86
|
+
<button class="px-4 py-2 bg-blue-600 text-white rounded">
|
|
87
|
+
Mở menu
|
|
88
|
+
</button>
|
|
89
|
+
</libs_ui-components-popover>
|
|
90
|
+
`,
|
|
91
|
+
})
|
|
92
|
+
export class ClickPopoverComponent {
|
|
93
|
+
protected popoverConfig = {
|
|
94
|
+
content: 'Nội dung menu',
|
|
95
|
+
direction: 'bottom' as const,
|
|
96
|
+
maxWidth: 200,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
private popoverControl?: IPopoverFunctionControlEvent;
|
|
100
|
+
|
|
101
|
+
handlerFunctionsControl(control: IPopoverFunctionControlEvent): void {
|
|
102
|
+
event?.stopPropagation();
|
|
103
|
+
this.popoverControl = control;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
handlerPopoverEvent(event: string): void {
|
|
107
|
+
event?.stopPropagation();
|
|
108
|
+
console.log('Popover event:', event);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
closePopover(): void {
|
|
112
|
+
this.popoverControl?.removePopoverOverlay();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Ví dụ 3 — Dùng ng-template cho nội dung tùy chỉnh
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
121
|
+
|
|
122
|
+
@Component({
|
|
123
|
+
standalone: true,
|
|
124
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
125
|
+
template: `
|
|
126
|
+
<div
|
|
127
|
+
LibsUiComponentsPopoverDirective
|
|
128
|
+
[config]="{ template: profileTpl, maxWidth: 280, direction: 'right' }">
|
|
129
|
+
<img src="avatar.png" alt="Avatar" class="w-10 h-10 rounded-full cursor-pointer" />
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<ng-template #profileTpl>
|
|
133
|
+
<div class="p-4">
|
|
134
|
+
<div class="font-bold text-gray-800">Nguyễn Văn A</div>
|
|
135
|
+
<div class="text-sm text-gray-500">nguyenvana@example.com</div>
|
|
136
|
+
<hr class="my-3" />
|
|
137
|
+
<button class="text-sm text-red-500">Đăng xuất</button>
|
|
138
|
+
</div>
|
|
139
|
+
</ng-template>
|
|
140
|
+
`,
|
|
141
|
+
})
|
|
142
|
+
export class ProfilePopoverComponent {}
|
|
66
143
|
```
|
|
67
144
|
|
|
68
|
-
|
|
145
|
+
### Ví dụ 4 — Chế độ text-ellipsis (chỉ hiện khi text bị cắt)
|
|
69
146
|
|
|
70
|
-
|
|
147
|
+
```typescript
|
|
148
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
71
149
|
|
|
72
|
-
|
|
150
|
+
@Component({
|
|
151
|
+
standalone: true,
|
|
152
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
153
|
+
template: `
|
|
154
|
+
<div
|
|
155
|
+
LibsUiComponentsPopoverDirective
|
|
156
|
+
[type]="'text'"
|
|
157
|
+
[config]="{ content: 'Đây là đoạn văn bản dài sẽ bị cắt ngắn khi container quá nhỏ', direction: 'top' }"
|
|
158
|
+
style="width: 120px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
159
|
+
Đây là đoạn văn bản dài sẽ bị cắt ngắn khi container quá nhỏ
|
|
160
|
+
</div>
|
|
161
|
+
`,
|
|
162
|
+
})
|
|
163
|
+
export class TextEllipsisPopoverComponent {}
|
|
164
|
+
```
|
|
73
165
|
|
|
74
|
-
|
|
75
|
-
| ------------------------------------ | ------------------------------ | ------------ | -------------------------------------------------------- |
|
|
76
|
-
| `[config]` | `IPopoverConfig` | **Bắt buộc** | Cấu hình nội dung, hướng và kích thước của popover. |
|
|
77
|
-
| `[mode]` | `'hover' \| 'click' \| 'none'` | `'hover'` | Chế độ kích hoạt hiển thị. |
|
|
78
|
-
| `[ignoreCursorPointerModeLikeClick]` | `boolean` | `false` | Bỏ qua việc đổi cursor thành pointer trong chế độ click. |
|
|
79
|
-
| `[ignoreStopPropagationEvent]` | `boolean` | `false` | Cho phép sự kiện click lan truyền ra lớp cha. |
|
|
80
|
-
| `[initEventInElementRefCustom]` | `ElementRef` | `undefined` | Gắn sự kiện vào một element khác thay vì host element. |
|
|
166
|
+
### Ví dụ 5 — Điều khiển từ bên ngoài (FunctionControl)
|
|
81
167
|
|
|
82
|
-
|
|
168
|
+
```typescript
|
|
169
|
+
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
170
|
+
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
|
|
83
171
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
172
|
+
@Component({
|
|
173
|
+
standalone: true,
|
|
174
|
+
imports: [LibsUiComponentsPopoverComponent],
|
|
175
|
+
template: `
|
|
176
|
+
<libs_ui-components-popover
|
|
177
|
+
[config]="{ content: 'Popover được mở từ bên ngoài', direction: 'bottom' }"
|
|
178
|
+
[mode]="'click'"
|
|
179
|
+
[ignoreShowPopover]="true"
|
|
180
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
181
|
+
<span>Phần tử target</span>
|
|
182
|
+
</libs_ui-components-popover>
|
|
183
|
+
|
|
184
|
+
<button (click)="handlerOpenPopover($event)">Mở Popover từ nút khác</button>
|
|
185
|
+
<button (click)="handlerClosePopover($event)">Đóng Popover</button>
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
export class ControlledPopoverComponent {
|
|
189
|
+
private popoverControl?: IPopoverFunctionControlEvent;
|
|
190
|
+
|
|
191
|
+
handlerFunctionsControl(control: IPopoverFunctionControlEvent): void {
|
|
192
|
+
event?.stopPropagation();
|
|
193
|
+
this.popoverControl = control;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
handlerOpenPopover(event: MouseEvent): void {
|
|
197
|
+
event.stopPropagation();
|
|
198
|
+
this.popoverControl?.showPopover();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
handlerClosePopover(event: MouseEvent): void {
|
|
202
|
+
event.stopPropagation();
|
|
203
|
+
this.popoverControl?.removePopoverOverlay();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## @Input()
|
|
209
|
+
|
|
210
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
211
|
+
|---|---|---|---|---|
|
|
212
|
+
| `[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' }"` |
|
|
213
|
+
| `[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'"` |
|
|
214
|
+
| `[type]` | `'text' \| 'other'` | `'other'` | Khi là `'text'`, popover chỉ hiển thị khi nội dung bị truncate (clientWidth >= scrollWidth). | `[type]="'text'"` |
|
|
215
|
+
| `[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()"` |
|
|
216
|
+
| `[elementRefCustom]` | `HTMLElement` | `undefined` | Element tùy chỉnh để gắn popover vào thay vì host element mặc định. | `[elementRefCustom]="myElementRef"` |
|
|
217
|
+
| `[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"` |
|
|
218
|
+
| `[classInclude]` | `string` | `''` | Các CSS class bổ sung sẽ được thêm vào host element. | `[classInclude]="'cursor-pointer text-blue-500'"` |
|
|
219
|
+
| `[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()"` |
|
|
220
|
+
| `[ignoreHiddenPopoverContentWhenMouseLeave]` | `boolean` | `undefined` | Khi `true`, không ẩn popover khi chuột rời khỏi content overlay. | `[ignoreHiddenPopoverContentWhenMouseLeave]="true"` |
|
|
221
|
+
| `[ignoreClickOutside]` | `boolean` | `undefined` | Khi `true`, không đóng popover khi click bên ngoài vùng popover. | `[ignoreClickOutside]="true"` |
|
|
222
|
+
| `[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"` |
|
|
223
|
+
| `[ignoreCursorPointerModeLikeClick]` | `boolean` | `undefined` | Khi `true`, không thêm class `cursor-pointer` vào host element khi `mode` là click. | `[ignoreCursorPointerModeLikeClick]="true"` |
|
|
224
|
+
| `[isAddContentToParentDocument]` | `boolean` | `undefined` | Khi `true`, render overlay vào parent document. Dùng khi popover nằm trong iframe. | `[isAddContentToParentDocument]="true"` |
|
|
225
|
+
| `[debugId]` | `string` | `undefined` | ID dùng cho mục đích debug. | `[debugId]="'user-avatar-popover'"` |
|
|
226
|
+
|
|
227
|
+
## @Output()
|
|
228
|
+
|
|
229
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
230
|
+
|---|---|---|---|---|
|
|
231
|
+
| `(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)"` |
|
|
232
|
+
| `(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)"` |
|
|
233
|
+
| `(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)"` |
|
|
234
|
+
| `(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)"` |
|
|
87
235
|
|
|
88
236
|
## Types & Interfaces
|
|
89
237
|
|
|
90
238
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
239
|
+
import {
|
|
240
|
+
IPopoverOverlay,
|
|
241
|
+
IPopoverFunctionControlEvent,
|
|
242
|
+
IFlagMouse,
|
|
243
|
+
IPopover,
|
|
244
|
+
TYPE_POPOVER_TYPE,
|
|
245
|
+
TYPE_POPOVER_MODE,
|
|
246
|
+
TYPE_POPOVER_DIRECTION,
|
|
247
|
+
TYPE_POPOVER_POSITION_MODE,
|
|
248
|
+
TYPE_POPOVER_EVENT,
|
|
249
|
+
} from '@libs-ui/components-popover';
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `IPopoverOverlay` — Cấu hình overlay
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { IPopoverOverlay } from '@libs-ui/components-popover';
|
|
256
|
+
|
|
257
|
+
export interface IPopoverOverlay {
|
|
258
|
+
// Nội dung
|
|
259
|
+
content?: string; // Nội dung dạng string HTML. Không dùng kèm template.
|
|
260
|
+
template?: TemplateRef<any>; // Nội dung dạng ng-template. Không dùng kèm content.
|
|
261
|
+
itemContext?: any; // Context truyền vào template khi dùng ng-template.
|
|
262
|
+
|
|
263
|
+
// Kích thước
|
|
264
|
+
width?: number; // Chiều rộng cố định (px)
|
|
265
|
+
minWidth?: number; // Chiều rộng tối thiểu (px)
|
|
266
|
+
maxWidth?: number; // Chiều rộng tối đa (px). Default: 250
|
|
267
|
+
maxHeight?: number | null; // Chiều cao tối đa (px). null = tự tính theo viewport. Default: 140
|
|
268
|
+
widthByParent?: boolean; // Nếu true, overlay rộng bằng phần tử cha
|
|
269
|
+
parentBorderWidth?: number; // Bù border của phần tử cha khi dùng widthByParent
|
|
270
|
+
|
|
271
|
+
// Vị trí & hướng
|
|
272
|
+
direction?: TYPE_POPOVER_DIRECTION; // Hướng ưu tiên. Default: 'bottom'
|
|
273
|
+
directionDistance?: number; // Khoảng cách bổ sung theo hướng (px)
|
|
274
|
+
position?: {
|
|
275
|
+
mode: TYPE_POPOVER_POSITION_MODE; // Căn chỉnh theo trục phụ: 'start' | 'center' | 'end'
|
|
276
|
+
distance: number; // Khoảng offset so với vị trí căn chỉnh (px)
|
|
277
|
+
autoUpdatePosition?: {
|
|
278
|
+
startDistance: number; // Offset khi mode tự chuyển sang 'start'
|
|
279
|
+
endDistance: number; // Offset khi mode tự chuyển sang 'end'
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Padding nội dung overlay
|
|
284
|
+
paddingTop?: number;
|
|
285
|
+
paddingRight?: number;
|
|
286
|
+
paddingBottom?: number;
|
|
287
|
+
paddingLeft?: number;
|
|
288
|
+
|
|
289
|
+
// Styling
|
|
290
|
+
classInclude?: string; // CSS class bổ sung cho wrapper overlay
|
|
291
|
+
classIncludeOverlayBody?: string; // CSS class bổ sung cho body overlay
|
|
292
|
+
whiteTheme?: boolean; // Dùng theme màu trắng
|
|
293
|
+
ngStyles?: Record<string, any>; // Inline styles bổ sung
|
|
294
|
+
|
|
295
|
+
// Mũi tên
|
|
296
|
+
ignoreArrow?: boolean; // Ẩn mũi tên chỉ hướng
|
|
297
|
+
arrowPositionCustom?: number; // Vị trí tùy chỉnh của mũi tên (px)
|
|
298
|
+
|
|
299
|
+
// Animation
|
|
300
|
+
animationConfig?: {
|
|
301
|
+
time?: number; // Thời gian animation (giây). Default: 0.4
|
|
302
|
+
distance?: number; // Khoảng cách trượt khi animate (px). Default: 12
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Hành vi
|
|
306
|
+
timerDestroy?: number; // Delay (ms) trước khi ẩn khi chuột rời. Default: 0
|
|
307
|
+
zIndex?: number; // z-index của overlay
|
|
308
|
+
isAddContentToParentDocument?: boolean; // Render vào parent document (dùng trong iframe)
|
|
309
|
+
scrollOverlayOptions?: IScrollOverlayOptions; // Tùy chọn scroll cho overlay
|
|
98
310
|
}
|
|
99
311
|
```
|
|
100
312
|
|
|
101
|
-
|
|
313
|
+
### `TYPE_POPOVER_MODE` — Chế độ kích hoạt
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { TYPE_POPOVER_MODE } from '@libs-ui/components-popover';
|
|
317
|
+
|
|
318
|
+
type TYPE_POPOVER_MODE =
|
|
319
|
+
| 'hover' // Hiển thị khi di chuột vào (mặc định)
|
|
320
|
+
| 'click' // Hiển thị khi click vào, không tự đóng
|
|
321
|
+
| 'click-toggle' // Click để mở, click lần nữa để đóng
|
|
322
|
+
| 'click_open_and_click_panel_content_hidden'; // Click để mở, click vào nội dung overlay để đóng
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `TYPE_POPOVER_DIRECTION` — Hướng hiển thị
|
|
102
326
|
|
|
103
|
-
|
|
327
|
+
```typescript
|
|
328
|
+
import { TYPE_POPOVER_DIRECTION } from '@libs-ui/components-popover';
|
|
104
329
|
|
|
105
|
-
|
|
|
106
|
-
|
|
107
|
-
| `.libs-ui-popover-container` | Lớp bao ngoài của nội dung popover. |
|
|
108
|
-
| `.libs-ui-popover-arrow` | Mũi tên chỉ hướng của popover. |
|
|
330
|
+
type TYPE_POPOVER_DIRECTION = 'top' | 'right' | 'bottom' | 'left';
|
|
331
|
+
```
|
|
109
332
|
|
|
110
|
-
|
|
333
|
+
Khi hướng ưu tiên không đủ chỗ trong viewport, popover tự động thử theo thứ tự fallback:
|
|
334
|
+
- `bottom` → `bottom`, `top`, `right`, `left`
|
|
335
|
+
- `top` → `top`, `bottom`, `right`, `left`
|
|
336
|
+
- `right` → `right`, `left`, `bottom`, `top`
|
|
337
|
+
- `left` → `left`, `right`, `bottom`, `top`
|
|
111
338
|
|
|
112
|
-
|
|
113
|
-
| --------------- | ------- | ------------------ |
|
|
114
|
-
| Angular | 18+ | Framework |
|
|
115
|
-
| CDK Overlay | 18+ | Positioning engine |
|
|
116
|
-
| Angular Signals | - | State management |
|
|
117
|
-
| TailwindCSS | 3.x | Styling |
|
|
339
|
+
### `TYPE_POPOVER_POSITION_MODE` — Căn chỉnh trục phụ
|
|
118
340
|
|
|
119
|
-
|
|
341
|
+
```typescript
|
|
342
|
+
import { TYPE_POPOVER_POSITION_MODE } from '@libs-ui/components-popover';
|
|
120
343
|
|
|
121
|
-
|
|
122
|
-
|
|
344
|
+
type TYPE_POPOVER_POSITION_MODE = 'start' | 'center' | 'end';
|
|
345
|
+
// 'start' → Căn về phía bắt đầu của phần tử gốc (trái khi direction là top/bottom)
|
|
346
|
+
// 'center' → Căn giữa so với phần tử gốc (mặc định khi không chỉ định)
|
|
347
|
+
// 'end' → Căn về phía cuối của phần tử gốc (phải khi direction là top/bottom)
|
|
123
348
|
```
|
|
124
349
|
|
|
125
|
-
|
|
350
|
+
### `TYPE_POPOVER_EVENT` — Sự kiện
|
|
126
351
|
|
|
127
|
-
|
|
352
|
+
```typescript
|
|
353
|
+
import { TYPE_POPOVER_EVENT } from '@libs-ui/components-popover';
|
|
354
|
+
|
|
355
|
+
type TYPE_POPOVER_EVENT =
|
|
356
|
+
| 'show' // Overlay vừa được hiển thị
|
|
357
|
+
| 'remove' // Overlay vừa bị xóa
|
|
358
|
+
| 'click' // Click vào host element
|
|
359
|
+
| 'mouseenter-element' // Chuột vào host element
|
|
360
|
+
| 'mouseleave-element' // Chuột rời khỏi host element
|
|
361
|
+
| 'mouseenter-content' // Chuột vào overlay content
|
|
362
|
+
| 'mouseleave-content'; // Chuột rời khỏi overlay content
|
|
363
|
+
```
|
|
128
364
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
365
|
+
### `IPopoverFunctionControlEvent` — API điều khiển
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
|
|
369
|
+
|
|
370
|
+
interface IPopoverFunctionControlEvent {
|
|
371
|
+
showPopover: (elementRef?: HTMLElement) => Promise<void>; // Hiển thị popover (tùy chọn gắn vào element khác)
|
|
372
|
+
removePopoverOverlay: () => Promise<void>; // Đóng và xóa overlay
|
|
373
|
+
updatePopoverOverlayPosition: () => Promise<void>; // Tính toán lại vị trí overlay
|
|
374
|
+
updatePopoverOverlay: () => Promise<void>; // Cập nhật config và vị trí overlay
|
|
375
|
+
getRectContainer: () => Promise<IBoundingClientRect>; // Lấy vị trí phần tử gốc
|
|
376
|
+
getRectContent: () => Promise<IBoundingClientRect>; // Lấy vị trí overlay content
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### `IFlagMouse` — Trạng thái chuột
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { IFlagMouse } from '@libs-ui/components-popover';
|
|
384
|
+
|
|
385
|
+
interface IFlagMouse {
|
|
386
|
+
isMouseEnter: boolean; // Chuột đang trong host element
|
|
387
|
+
isMouseEnterContent: boolean; // Chuột đang trong overlay content
|
|
388
|
+
isContainerHasScroll?: boolean; // Host element có scroll
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### `IPopover` — Interface tổng hợp
|
|
132
393
|
|
|
133
|
-
|
|
134
|
-
|
|
394
|
+
```typescript
|
|
395
|
+
import { IPopover } from '@libs-ui/components-popover';
|
|
396
|
+
|
|
397
|
+
interface IPopover {
|
|
398
|
+
type?: TYPE_POPOVER_TYPE;
|
|
399
|
+
mode?: TYPE_POPOVER_MODE;
|
|
400
|
+
dataView?: string;
|
|
401
|
+
config?: IPopoverOverlay;
|
|
402
|
+
classInclude?: string;
|
|
403
|
+
ignoreShowPopover?: boolean;
|
|
404
|
+
ignoreHiddenPopoverContentWhenMouseLeave?: boolean;
|
|
405
|
+
elementRefCustom?: HTMLElement;
|
|
406
|
+
isAddContentToParentDocument?: boolean;
|
|
407
|
+
templateCustomContent?: TemplateRef<any>;
|
|
408
|
+
}
|
|
135
409
|
```
|
|
136
410
|
|
|
137
|
-
##
|
|
411
|
+
## Lưu ý quan trọng
|
|
412
|
+
|
|
413
|
+
⚠️ **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[]`.
|
|
414
|
+
|
|
415
|
+
⚠️ **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.
|
|
416
|
+
|
|
417
|
+
⚠️ **`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).
|
|
418
|
+
|
|
419
|
+
⚠️ **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.
|
|
420
|
+
|
|
421
|
+
⚠️ **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.
|
|
422
|
+
|
|
423
|
+
⚠️ **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`.
|
|
424
|
+
|
|
425
|
+
## Demo
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
npx nx serve core-ui
|
|
429
|
+
```
|
|
138
430
|
|
|
139
|
-
|
|
431
|
+
Truy cập: http://localhost:4500/components/popover
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export {};
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9wb3Zlci5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvcG9wb3Zlci9zcmMvaW50ZXJmYWNlcy9wb3BvdmVyLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuaW1wb3J0IHsgVGVtcGxhdGVSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IElQb3BvdmVyT3ZlcmxheSB9IGZyb20gJy4vb3ZlcmxheS5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgVFlQRV9QT1BPVkVSX1RZUEUsIFRZUEVfUE9QT1ZFUl9NT0RFIH0gZnJvbSAnLi9wb3BvdmVyLnR5cGUnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElQb3BvdmVyIHtcbiAgdHlwZT86IFRZUEVfUE9QT1ZFUl9UWVBFO1xuICBtb2RlPzogVFlQRV9QT1BPVkVSX01PREU7XG4gIGRhdGFWaWV3Pzogc3RyaW5nO1xuICBjb25maWc/
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9wb3Zlci5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvcG9wb3Zlci9zcmMvaW50ZXJmYWNlcy9wb3BvdmVyLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuaW1wb3J0IHsgVGVtcGxhdGVSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IElQb3BvdmVyT3ZlcmxheSB9IGZyb20gJy4vb3ZlcmxheS5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgVFlQRV9QT1BPVkVSX1RZUEUsIFRZUEVfUE9QT1ZFUl9NT0RFIH0gZnJvbSAnLi9wb3BvdmVyLnR5cGUnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElQb3BvdmVyIHtcbiAgdHlwZT86IFRZUEVfUE9QT1ZFUl9UWVBFO1xuICBtb2RlPzogVFlQRV9QT1BPVkVSX01PREU7XG4gIGRhdGFWaWV3Pzogc3RyaW5nO1xuICBjb25maWc/OiBJUG9wb3Zlck92ZXJsYXk7XG4gIGNsYXNzSW5jbHVkZT86IHN0cmluZztcbiAgaWdub3JlU2hvd1BvcG92ZXI/OiBib29sZWFuO1xuICBpZ25vcmVIaWRkZW5Qb3BvdmVyQ29udGVudFdoZW5Nb3VzZUxlYXZlPzogYm9vbGVhbjtcbiAgZWxlbWVudFJlZkN1c3RvbT86IEhUTUxFbGVtZW50O1xuICBpc0FkZENvbnRlbnRUb1BhcmVudERvY3VtZW50PzogYm9vbGVhbjtcbiAgdGVtcGxhdGVDdXN0b21Db250ZW50PzogVGVtcGxhdGVSZWY8YW55Pjtcbn1cbiJdfQ==
|