@libs-ui/components-popover 0.2.356-9 → 0.2.357-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 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 một phần tử gốc, hỗ trợ nhiều hướng và chế độ kích hoạt.
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
- Bộ công cụ Popover cung cấp giải pháp hiển thị thông tin ngữ cảnh linh hoạt. Bạn thể sử dụng dưới dạng `Directive` để tiết kiệm DOM hoặc `Component Selector` khi cần bọc nhiều phần tử phức tạp.
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
- ### Tính năng
9
+ ## Tính năng
10
10
 
11
- - ✅ **Linh hoạt cách dùng**: Hỗ trợ cả Directive và Component Selector.
12
- - ✅ **Định vị thông minh**: Tự động tính toán hướng để không bị tràn khung nhìn (viewport).
13
- - ✅ **Template Support**: Hỗ trợ hiển thị nội dung từ `string` đơn giản đến `ng-template` phức tạp.
14
- - ✅ **Event Handling**: Quản tốt các sự kiện hover, click và click-outside.
15
- - ✅ **Customizable**: Dễ dàng tùy chỉnh chiều rộng, class CSS hướng hiển thị.
16
- - ✅ **OnPush Change Detection**
17
- - ✅ **Angular Signals**
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 (Tooltips) khi di chuột qua phần tử.
22
- - Hiển thị menu hoặc nội dung chi tiết khi nhấn vào nút.
23
- - Thông báo trạng thái hoặc hướng dẫn người dùng tại chỗ.
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 { LibsUiComponentsPopoverModule } from '@libs-ui/components-popover';
38
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
39
39
 
40
40
  @Component({
41
41
  standalone: true,
42
- imports: [LibsUiComponentsPopoverModule],
42
+ imports: [LibsUiComponentsPopoverComponent],
43
43
  // ...
44
44
  })
45
45
  export class YourComponent {}
46
46
  ```
47
47
 
48
- ## dụ
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 (Sử dụng phổ biến)
52
+ ### Ví dụ 1 — Dạng Directive (Tooltip đơn giản)
51
53
 
52
- ```html
53
- <div
54
- LibsUiComponentsPopoverDirective
55
- [config]="{ content: 'Nội dung thông báo' }">
56
- Hover me
57
- </div>
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
- ```html
63
- <libs_ui-components-popover [config]="{ content: 'Nội dung thông báo', direction: 'top' }">
64
- <button>Action</button>
65
- </libs_ui-components-popover>
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
- ## API
145
+ ### Ví dụ 4 — Chế độ text-ellipsis (chỉ hiện khi text bị cắt)
69
146
 
70
- ### LibsUiComponentsPopoverDirective / LibsUiComponentsPopoverComponent
147
+ ```typescript
148
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
71
149
 
72
- #### Inputs
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
- | Property | Type | Default | Description |
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
+ ### dụ 5 — Điều khiển từ bên ngoài (FunctionControl)
81
167
 
82
- #### Outputs
168
+ ```typescript
169
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
170
+ import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
83
171
 
84
- | Property | Type | Description |
85
- | --------------- | --------- | --------------------------------------- |
86
- | `(outExpanded)` | `boolean` | Phát ra trạng thái đóng/mở của popover. |
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
- export interface IPopoverConfig {
92
- content?: string;
93
- template?: TemplateRef<any>;
94
- direction?: 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
95
- width?: number;
96
- classCustom?: string;
97
- context?: any;
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
- ## Styling
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
- ### CSS Classes
327
+ ```typescript
328
+ import { TYPE_POPOVER_DIRECTION } from '@libs-ui/components-popover';
104
329
 
105
- | Class | Description |
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
- ## Công nghệ
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
- | Technology | Version | Purpose |
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
- ## Demo
341
+ ```typescript
342
+ import { TYPE_POPOVER_POSITION_MODE } from '@libs-ui/components-popover';
120
343
 
121
- ```bash
122
- npx nx serve core-ui
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
- Truy cập: `http://localhost:4200/popover`
350
+ ### `TYPE_POPOVER_EVENT` — Sự kiện
126
351
 
127
- ## Unit Tests
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
- ```bash
130
- # Chạy tests
131
- npx nx test components-popover
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
- # Coverage
134
- npx nx test components-popover --coverage
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
- ## License
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
- MIT
431
+ Truy cập: http://localhost:4500/components/popover
@@ -1,2 +1,2 @@
1
1
  export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9wb3Zlci5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvcG9wb3Zlci9zcmMvaW50ZXJmYWNlcy9wb3BvdmVyLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuaW1wb3J0IHsgVGVtcGxhdGVSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IElQb3BvdmVyT3ZlcmxheSB9IGZyb20gJy4vb3ZlcmxheS5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgVFlQRV9QT1BPVkVSX1RZUEUsIFRZUEVfUE9QT1ZFUl9NT0RFIH0gZnJvbSAnLi9wb3BvdmVyLnR5cGUnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElQb3BvdmVyIHtcbiAgdHlwZT86IFRZUEVfUE9QT1ZFUl9UWVBFO1xuICBtb2RlPzogVFlQRV9QT1BPVkVSX01PREU7XG4gIGRhdGFWaWV3Pzogc3RyaW5nO1xuICBjb25maWc/OiBJUG9wb3Zlck92ZXJsYXkgfCB1bmRlZmluZWQ7XG4gIGNsYXNzSW5jbHVkZT86IHN0cmluZztcbiAgaWdub3JlU2hvd1BvcG92ZXI/OiBib29sZWFuO1xuICBpZ25vcmVIaWRkZW5Qb3BvdmVyQ29udGVudFdoZW5Nb3VzZUxlYXZlPzogYm9vbGVhbjtcbiAgZWxlbWVudFJlZkN1c3RvbT86IEhUTUxFbGVtZW50O1xuICBpc0FkZENvbnRlbnRUb1BhcmVudERvY3VtZW50PzogYm9vbGVhbjtcbiAgdGVtcGxhdGVDdXN0b21Db250ZW50PzogVGVtcGxhdGVSZWY8YW55PiB8IHVuZGVmaW5lZDtcbn1cbiJdfQ==
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9wb3Zlci5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvcG9wb3Zlci9zcmMvaW50ZXJmYWNlcy9wb3BvdmVyLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuaW1wb3J0IHsgVGVtcGxhdGVSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IElQb3BvdmVyT3ZlcmxheSB9IGZyb20gJy4vb3ZlcmxheS5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgVFlQRV9QT1BPVkVSX1RZUEUsIFRZUEVfUE9QT1ZFUl9NT0RFIH0gZnJvbSAnLi9wb3BvdmVyLnR5cGUnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElQb3BvdmVyIHtcbiAgdHlwZT86IFRZUEVfUE9QT1ZFUl9UWVBFO1xuICBtb2RlPzogVFlQRV9QT1BPVkVSX01PREU7XG4gIGRhdGFWaWV3Pzogc3RyaW5nO1xuICBjb25maWc/OiBJUG9wb3Zlck92ZXJsYXk7XG4gIGNsYXNzSW5jbHVkZT86IHN0cmluZztcbiAgaWdub3JlU2hvd1BvcG92ZXI/OiBib29sZWFuO1xuICBpZ25vcmVIaWRkZW5Qb3BvdmVyQ29udGVudFdoZW5Nb3VzZUxlYXZlPzogYm9vbGVhbjtcbiAgZWxlbWVudFJlZkN1c3RvbT86IEhUTUxFbGVtZW50O1xuICBpc0FkZENvbnRlbnRUb1BhcmVudERvY3VtZW50PzogYm9vbGVhbjtcbiAgdGVtcGxhdGVDdXN0b21Db250ZW50PzogVGVtcGxhdGVSZWY8YW55Pjtcbn1cbiJdfQ==