@libs-ui/components-pages-template-full-screen 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,136 +1,516 @@
1
1
  # @libs-ui/components-pages-template-full-screen
2
2
 
3
- > Khung layout chuẩn cho các trang hiển thị toàn màn hình (Full Screen), tích hợp header với các action linh hoạt.
3
+ > Khung layout toàn màn hình (Full Screen) cho Angular — tích hợp header đa vùng với action linh hoạt, giao tiếp micro-frontend, và hỗ trợ lazy load body component (V2).
4
4
 
5
5
  ## Giới thiệu
6
6
 
7
- `LibsUiComponentsPagesTemplateFullScreenComponent` một standalone Angular component cung cấp cấu trúc layout cho các module cần không gian hiển thị toàn màn hình như: Tạo mới đối tượng phức tạp, Workflow editor, hoặc các trang báo cáo chuyên sâu.
7
+ Package này cung cấp hai component layout toàn màn hình cho Angular:
8
8
 
9
- ### Tính năng
9
+ - **`LibsUiComponentsPagesTemplateFullScreenComponent`** (V1 — `@deprecated`): Component gốc, sử dụng `ng-content` cho phần body. Phù hợp với codebase cũ nhưng không còn được khuyến khích dùng cho code mới.
10
+ - **`LibsUiComponentsPagesTemplateFullScreenV2Component`** (V2 — khuyến dùng): Phiên bản nâng cấp, hỗ trợ lazy load body component động qua `bodyConfig`, skeleton tự động trong lúc chờ load, và `FunctionsControl` API để điều khiển từ component con.
10
11
 
11
- - **Header linh hoạt**: Hỗ trợ 3 phân vùng (Trái, Giữa, Phải).
12
- - ✅ **Đa dạng Action**: Tích hợp sẵn Button, Switch, Trạng thái (Status), Label và Dropdown menu trên header.
13
- - **Tùy biến cao**: Hỗ trợ Content Projection cho nội dung chính và TemplateRef cho các vùng trên header.
14
- - ✅ **Giao tiếp Micro**: Tự động gửi message "open/close" và cập nhật trạng thái hoạt động tới ứng dụng cha.
15
- - **Angular Signals**: Tương thích hoàn toàn với Angular 18 Signals.
12
+ Cả hai đều tích hợp sẵn chế `PostMessage` để đồng bộ trạng thái "đang mở" với ứng dụng cha (micro-frontend host).
13
+
14
+ ## Tính năng
15
+
16
+ - Header 3 vùng (Trái / Giữa / Phải) với phân chia class tùy chỉnh
17
+ - Hỗ trợ các loại action trên header: `button`, `button-status`, `switch`, `label`, `line-space`, `button-dropdown`
18
+ - TemplateRef tùy chỉnh cho từng vùng header (`leftTemplate`, `centerTemplate`, `templateRight`)
19
+ - Menu dropdown "ba chấm" tích hợp sẵn (`menuDropDownConfigs`)
20
+ - Tự động escape HTML cho tiêu đề (XSS protection)
21
+ - Giao tiếp micro-frontend: gửi `PostMessage open/close` khi mount/unmount, ping định kỳ mỗi 2 giây
22
+ - (V2) Lazy load body component qua `Observable<Type<any>>`
23
+ - (V2) Skeleton tự động hiển thị trong lúc chờ Observable resolve
24
+ - (V2) `FunctionsControl` — API để component con chủ động đóng page
25
+ - (V2) `ChangeDetectionStrategy.OnPush` + Angular Signals
16
26
 
17
27
  ## Khi nào sử dụng
18
28
 
19
- - Khi cần không gian hiển thị tối đa cho người dùng (ví dụ: màn hình cấu hình workflow, chỉnh sửa ảnh/video).
20
- - Khi trang cấu trúc header phức tạp với nhiều nút chức năng điều hướng và thao tác nhanh.
21
- - Khi cần một màn hình "Modal-like" nhưng chiếm trọn viewport.
29
+ - Xây dựng màn hình cấu hình workflow, editor chuyên sâu chiếm trọn viewport
30
+ - Các trang báo cáo dữ liệu lớn cần tối đa không gian hiển thị
31
+ - Tạo mới thực thể phức tạp với quy trình nhiều bước (wizard-style)
32
+ - Khi cần đồng bộ trạng thái "đang mở" với ứng dụng cha qua `PostMessage`
33
+ - Khi muốn mở trang full screen từ TypeScript (không cần selector HTML) thông qua `LibsUiDynamicComponentService`
34
+ - Khi body cần được lazy load theo bundle split với skeleton trong lúc chờ (dùng V2)
22
35
 
23
36
  ## Cài đặt
24
37
 
25
38
  ```bash
26
- # npm
27
39
  npm install @libs-ui/components-pages-template-full-screen
28
-
29
- # yarn
30
- yarn add @libs-ui/components-pages-template-full-screen
31
40
  ```
32
41
 
33
42
  ## Import
34
43
 
35
44
  ```typescript
45
+ // V2 (khuyến dùng)
46
+ import { LibsUiComponentsPagesTemplateFullScreenV2Component } from '@libs-ui/components-pages-template-full-screen';
47
+
48
+ // V1 (deprecated — chỉ dùng khi maintain code cũ)
36
49
  import { LibsUiComponentsPagesTemplateFullScreenComponent } from '@libs-ui/components-pages-template-full-screen';
37
50
 
51
+ // Types & Interfaces
52
+ import {
53
+ IPagesTemplateFullScreenButton,
54
+ IPagesTemplateFullScreenButtonKey,
55
+ IFullScreenV2BodyConfig,
56
+ IFullScreenV2SectionData,
57
+ IFunctionControlPageFullScreen,
58
+ } from '@libs-ui/components-pages-template-full-screen';
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Ví dụ sử dụng — V1 (cơ bản, dùng ng-content)
64
+
65
+ ### Ví dụ 1: Trang full screen đơn giản
66
+
67
+ ```typescript
68
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
69
+ import {
70
+ IPagesTemplateFullScreenButton,
71
+ LibsUiComponentsPagesTemplateFullScreenComponent,
72
+ } from '@libs-ui/components-pages-template-full-screen';
73
+
38
74
  @Component({
75
+ selector: 'app-campaign-create',
39
76
  standalone: true,
77
+ changeDetection: ChangeDetectionStrategy.OnPush,
40
78
  imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
41
- // ...
79
+ template: `
80
+ <libs_ui-components-pages_template_full_screen
81
+ [title]="'Tạo chiến dịch Marketing'"
82
+ [labelLeft]="'Hủy bỏ'"
83
+ [buttonRight]="buttonRight"
84
+ (outClose)="handlerClose()"
85
+ (outClickButton)="handlerClickButton($event)">
86
+ <div class="p-[24px]">
87
+ <h2 class="libs-ui-font-h3s">Bước 1: Thông tin cơ bản</h2>
88
+ <!-- Nội dung trang -->
89
+ </div>
90
+ </libs_ui-components-pages_template_full_screen>
91
+ `,
42
92
  })
43
- export class YourComponent {}
93
+ export class AppCampaignCreateComponent {
94
+ readonly buttonRight: IPagesTemplateFullScreenButton[] = [
95
+ { key: 'button', label: 'Lưu nháp', type: 'button-secondary' },
96
+ { key: 'button', label: 'Tiếp tục', type: 'button-primary' },
97
+ ];
98
+
99
+ handlerClose(): void {
100
+ // event.stopPropagation() không cần thiết ở đây vì outClose không bubble DOM
101
+ console.log('Đóng trang full screen');
102
+ }
103
+
104
+ handlerClickButton(button: IPagesTemplateFullScreenButton): void {
105
+ if (button.label === 'Tiếp tục') {
106
+ // Xử lý chuyển bước
107
+ }
108
+ }
109
+ }
44
110
  ```
45
111
 
46
- ## Ví dụ
112
+ ### Ví dụ 2: Header với switch, dropdown menu và edit icon
47
113
 
48
- ### Basic
114
+ ```typescript
115
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
116
+ import {
117
+ IPagesTemplateFullScreenButton,
118
+ LibsUiComponentsPagesTemplateFullScreenComponent,
119
+ } from '@libs-ui/components-pages-template-full-screen';
120
+ import { IDropdown, IEmitSelectKey } from '@libs-ui/components-dropdown';
49
121
 
50
- ```html
51
- <libs_ui-components-pages_template_full_screen
52
- [title]="'Cấu hình chiến dịch mới'"
53
- [labelLeft]="'Quay lại'"
54
- (outClose)="onClose()">
55
- <div class="content">
56
- <!-- Nội dung trang của bạn -->
57
- </div>
58
- </libs_ui-components-pages_template_full_screen>
122
+ @Component({
123
+ selector: 'app-report-editor',
124
+ standalone: true,
125
+ changeDetection: ChangeDetectionStrategy.OnPush,
126
+ imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
127
+ template: `
128
+ <libs_ui-components-pages_template_full_screen
129
+ [title]="'Báo cáo doanh thu Q2/2026'"
130
+ [hasEdit]="true"
131
+ [buttonRight]="buttonRight"
132
+ [menuDropDownConfigs]="menuConfig"
133
+ [ignoreClosePageFullEvent]="true"
134
+ (outClose)="handlerClose()"
135
+ (outEdit)="handlerEdit()"
136
+ (outClickButton)="handlerClickButton($event)"
137
+ (outSelectedMenuDropdown)="handlerMenuSelect($event)">
138
+ <div class="p-[24px]">
139
+ <!-- Nội dung báo cáo -->
140
+ </div>
141
+ </libs_ui-components-pages_template_full_screen>
142
+ `,
143
+ })
144
+ export class AppReportEditorComponent {
145
+ readonly isPublished = signal(false);
146
+
147
+ readonly buttonRight: IPagesTemplateFullScreenButton[] = [
148
+ {
149
+ key: 'switch',
150
+ active: false,
151
+ classInclude: 'mr-[8px]',
152
+ action: async (event) => {
153
+ this.isPublished.set(event?.state ?? false);
154
+ },
155
+ },
156
+ { key: 'line-space' },
157
+ { key: 'button', label: 'Xuất PDF', type: 'button-secondary' },
158
+ { key: 'button', label: 'Lưu', type: 'button-primary' },
159
+ ];
160
+
161
+ readonly menuConfig: IDropdown = {
162
+ listConfig: {
163
+ type: 'text',
164
+ httpRequestData: signal({
165
+ objectInstance: {
166
+ list: [
167
+ { key: 'duplicate', label: 'Nhân bản' },
168
+ { key: 'archive', label: 'Lưu trữ' },
169
+ { key: 'delete', label: 'Xóa vĩnh viễn' },
170
+ ],
171
+ },
172
+ functionName: 'list',
173
+ argumentsValue: [],
174
+ }),
175
+ configTemplateText: signal({
176
+ fieldKey: 'key',
177
+ notUseVirtualScroll: true,
178
+ getConfigButtonLeft: (item: { key: string; label: string }) => ({
179
+ label: item.label,
180
+ type: 'button-link-third',
181
+ classLabel: 'libs-ui-font-h5r',
182
+ }),
183
+ }),
184
+ },
185
+ };
186
+
187
+ handlerClose(): void {
188
+ console.log('Đóng trang');
189
+ }
190
+
191
+ handlerEdit(): void {
192
+ console.log('Mở chỉnh sửa tiêu đề');
193
+ }
194
+
195
+ handlerClickButton(button: IPagesTemplateFullScreenButton): void {
196
+ console.log('Click button:', button.label);
197
+ }
198
+
199
+ handlerMenuSelect(event: IEmitSelectKey | undefined): void {
200
+ console.log('Menu chọn:', event?.key);
201
+ }
202
+ }
59
203
  ```
60
204
 
61
- ### With Header Actions
205
+ ---
62
206
 
63
- ```html
64
- <libs_ui-components-pages_template_full_screen
65
- [title]="'Chỉnh sửa nội dung'"
66
- [buttonRight]="[
67
- { key: 'switch', active: true, classInclude: 'mr-2' },
68
- { key: 'button', label: 'Lưu nháp', type: 'button-secondary' },
69
- { key: 'button', label: 'Xuất bản', type: 'button-primary' }
70
- ]"
71
- (outClickButton)="onAction($event)">
72
- <p>Nội dung soạn thảo...</p>
73
- </libs_ui-components-pages_template_full_screen>
207
+ ## Ví dụ sử dụng — V2 (khuyến dùng, mở động qua DynamicComponentService)
208
+
209
+ ### dụ 3: Mở full screen V2 từ TypeScript với lazy load body
210
+
211
+ ```typescript
212
+ import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
213
+ import { from, of } from 'rxjs';
214
+ import {
215
+ IFullScreenV2BodyConfig,
216
+ IPagesTemplateFullScreenButton,
217
+ LibsUiComponentsPagesTemplateFullScreenV2Component,
218
+ } from '@libs-ui/components-pages-template-full-screen';
219
+ import { ISkeletonConfig } from '@libs-ui/components-skeleton';
220
+ import { LibsUiDynamicComponentService, setInputs } from '@libs-ui/services-dynamic-component';
221
+
222
+ @Component({
223
+ selector: 'app-order-list',
224
+ standalone: true,
225
+ changeDetection: ChangeDetectionStrategy.OnPush,
226
+ template: `
227
+ <button (click)="handlerOpenDetail()">Xem chi tiết đơn hàng</button>
228
+ `,
229
+ })
230
+ export class AppOrderListComponent {
231
+ private readonly dynamicService = inject(LibsUiDynamicComponentService);
232
+
233
+ readonly skeletonConfig = signal<ISkeletonConfig>({
234
+ repeat: 1,
235
+ rows: [
236
+ { item: { classInclude: 'w-full h-[56px] rounded-lg mb-[8px]' } },
237
+ {
238
+ cols: [
239
+ { item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
240
+ { item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
241
+ { item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
242
+ ],
243
+ },
244
+ { item: { classInclude: 'w-full h-[64px] rounded-xl mt-[16px]' } },
245
+ ],
246
+ });
247
+
248
+ readonly bodyConfig: IFullScreenV2BodyConfig = {
249
+ getComponentOutlet: () =>
250
+ from(import('./order-detail/order-detail.component').then((c) => c.OrderDetailComponent)),
251
+ getDataComponentOutlet: (sectionData) =>
252
+ of({ disable: sectionData.item.disable, orderId: 'OD-4521' }),
253
+ skeletonConfig: this.skeletonConfig,
254
+ classInclude: 'overflow-y-auto',
255
+ };
256
+
257
+ readonly buttonRight: IPagesTemplateFullScreenButton[] = [
258
+ { key: 'button', label: 'In hóa đơn', type: 'button-secondary' },
259
+ { key: 'button', label: 'Xác nhận giao', type: 'button-primary' },
260
+ ];
261
+
262
+ handlerOpenDetail(): void {
263
+ const fullScreenRef = this.dynamicService.resolveComponentFactory(
264
+ LibsUiComponentsPagesTemplateFullScreenV2Component,
265
+ );
266
+
267
+ setInputs(fullScreenRef, {
268
+ title: 'Chi tiết đơn hàng #OD-4521',
269
+ labelLeft: 'Quay lại',
270
+ buttonRight: this.buttonRight,
271
+ bodyConfig: this.bodyConfig,
272
+ zIndex: 9999,
273
+ });
274
+
275
+ fullScreenRef.instance.outClose.subscribe(() => {
276
+ this.dynamicService.delete(fullScreenRef);
277
+ });
278
+
279
+ document.getElementsByTagName('main')?.[0]?.classList.add('relative');
280
+ this.dynamicService.addToElement(fullScreenRef, document.getElementsByTagName('main')?.[0]);
281
+ }
282
+ }
74
283
  ```
75
284
 
76
- ## API
77
-
78
- ### libs_ui-components-pages_template_full_screen
79
-
80
- #### Inputs
81
-
82
- | Property | Type | Default | Description |
83
- | ---------------------------- | ---------------------------------- | --------------------- | ------------------------------------------------------- |
84
- | `[title]` | `string` | `undefined` | Tiêu đề hiển thị ở giữa header. |
85
- | `[labelLeft]` | `string` | `'i18n_back_to_list'` | Nhãn cho nút quay lại bên trái. |
86
- | `[buttonCenter]` | `IPagesTemplateFullScreenButton[]` | `undefined` | Danh sách các nút hiển thị ở giữa header (cạnh title). |
87
- | `[buttonRight]` | `IPagesTemplateFullScreenButton[]` | `undefined` | Danh sách các nút hiển thị bên phải header. |
88
- | `[hasEdit]` | `boolean` | `false` | Hiển thị icon edit cạnh tiêu đề. |
89
- | `[menuDropDownConfigs]` | `IDropdown` | `undefined` | Cấu hình cho menu "Thanh thao tác khác" (ba chấm dọc). |
90
- | `[zIndex]` | `number` | `1000` | Thứ tự hiển thị của lớp overlay. |
91
- | `[ignoreClosePageFullEvent]` | `boolean` | `false` | Nếu true, sẽ không gửi message communicate tới app cha. |
92
- | `[ignoreBackgroundColor]` | `boolean` | `false` | Nếu true, nền sẽ trong suốt (mặc định #f2f5f7). |
93
- | `[disable]` | `boolean` | `false` | Vô hiệu hóa toàn bộ action trên header. |
94
- | `[leftTemplate]` | `TemplateRef<any>` | `undefined` | Custom template cho vùng bên trái header. |
95
- | `[centerTemplate]` | `TemplateRef<any>` | `undefined` | Custom template cho vùng giữa header. |
96
- | `[templateRight]` | `TemplateRef<any>` | `undefined` | Custom template cho vùng bên phải header. |
97
- | `[classHeaderInclude]` | `string` | `undefined` | Custom class cho header. |
98
- | `[classBodyInclude]` | `string` | `undefined` | Custom class cho thân trang. |
99
- | `[divideClassHeader]` | `object` | `undefined` | Cấu hình class cho các vùng phân chia trên header. |
100
-
101
- #### Outputs
102
-
103
- | Property | Type | Description |
104
- | --------------------------- | -------------------------------- | ----------------------------------------------------------------------------- |
105
- | `(outClose)` | `void` | Phát ra khi click nút quay lại/đóng. |
106
- | `(outEdit)` | `void` | Phát ra khi click icon edit cạnh tiêu đề. |
107
- | `(outSelectedMenuDropdown)` | `IEmitSelectKey` | Phát ra khi chọn item từ menu dropdown phụ. |
108
- | `(outClickButton)` | `IPagesTemplateFullScreenButton` | Phát ra khi click vào bất kỳ nút nào trong `buttonCenter` hoặc `buttonRight`. |
285
+ ### Ví dụ 4: Sử dụng FunctionsControl để đóng page từ component con
109
286
 
110
- ## Types & Interfaces
287
+ ```typescript
288
+ // Trong parent component — lưu ref để gọi FunctionsControl
289
+ import { ComponentRef } from '@angular/core';
290
+ import { LibsUiComponentsPagesTemplateFullScreenV2Component } from '@libs-ui/components-pages-template-full-screen';
291
+
292
+ // Khai báo ở cấp class (không phải const trong hàm)
293
+ private fullScreenRef: ComponentRef<LibsUiComponentsPagesTemplateFullScreenV2Component> | undefined;
294
+
295
+ handlerOpen(): void {
296
+ this.fullScreenRef = this.dynamicService.resolveComponentFactory(
297
+ LibsUiComponentsPagesTemplateFullScreenV2Component,
298
+ );
299
+
300
+ // Lấy FunctionsControl sau khi có ref
301
+ const fc = this.fullScreenRef.instance.FunctionsControl;
302
+
303
+ setInputs(this.fullScreenRef, {
304
+ title: 'Quy trình phê duyệt',
305
+ bodyConfig: {
306
+ getComponentOutlet: () =>
307
+ from(import('./approval-form.component').then((c) => c.ApprovalFormComponent)),
308
+ getDataComponentOutlet: () =>
309
+ of({ onClose: () => fc.emitClosePageFullEvent() }),
310
+ },
311
+ });
312
+
313
+ this.fullScreenRef.instance.outClose.subscribe(() => {
314
+ this.dynamicService.delete(this.fullScreenRef!);
315
+ this.fullScreenRef = undefined;
316
+ });
317
+
318
+ this.dynamicService.addToElement(this.fullScreenRef, document.body);
319
+ }
320
+
321
+ ngOnDestroy(): void {
322
+ if (this.fullScreenRef) {
323
+ this.dynamicService.delete(this.fullScreenRef);
324
+ }
325
+ }
326
+ ```
327
+
328
+ ### Ví dụ 5: Tùy chỉnh phân vùng header với TemplateRef
329
+
330
+ ```typescript
331
+ import { ChangeDetectionStrategy, Component, TemplateRef, viewChild } from '@angular/core';
332
+ import { LibsUiComponentsPagesTemplateFullScreenComponent } from '@libs-ui/components-pages-template-full-screen';
333
+
334
+ @Component({
335
+ selector: 'app-editor',
336
+ standalone: true,
337
+ changeDetection: ChangeDetectionStrategy.OnPush,
338
+ imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
339
+ template: `
340
+ <ng-template #customCenter>
341
+ <div class="flex items-center gap-[8px]">
342
+ <span class="libs-ui-font-h3s libs-ui-color-primary">Soạn thảo nội dung</span>
343
+ <span class="libs-ui-badge-status-success">Đang lưu...</span>
344
+ </div>
345
+ </ng-template>
346
+
347
+ <libs_ui-components-pages_template_full_screen
348
+ [centerTemplate]="customCenter"
349
+ [buttonRight]="[{ key: 'button', label: 'Xuất bản', type: 'button-primary' }]"
350
+ (outClose)="handlerClose()"
351
+ (outClickButton)="handlerClickButton($event)">
352
+ <div class="p-[24px]">Nội dung editor...</div>
353
+ </libs_ui-components-pages_template_full_screen>
354
+ `,
355
+ })
356
+ export class AppEditorComponent {
357
+ handlerClose(): void {
358
+ console.log('Đóng editor');
359
+ }
360
+
361
+ handlerClickButton(): void {
362
+ console.log('Xuất bản');
363
+ }
364
+ }
365
+ ```
366
+
367
+ ---
368
+
369
+ ## @Input() — V1 (`libs_ui-components-pages_template_full_screen`)
370
+
371
+ | Input | Type | Default | Mô tả | Ví dụ |
372
+ |---|---|---|---|---|
373
+ | `[title]` | `string` | `undefined` | Tiêu đề hiển thị ở giữa header, tự động escape HTML | `[title]="'Cấu hình chiến dịch'"` |
374
+ | `[labelLeft]` | `string` | `'i18n_back_to_list'` | Nhãn nút quay lại bên trái header | `[labelLeft]="'Hủy bỏ'"` |
375
+ | `[buttonCenter]` | `IPagesTemplateFullScreenButton[]` | `undefined` | Danh sách button/switch/label hiển thị ở giữa header (cạnh title) | `[buttonCenter]="centerButtons"` |
376
+ | `[buttonRight]` | `IPagesTemplateFullScreenButton[]` | `undefined` | Danh sách button/switch/label/line-space ở bên phải header | `[buttonRight]="rightButtons"` |
377
+ | `[hasEdit]` | `boolean` | `false` | Hiển thị icon chỉnh sửa cạnh tiêu đề, phát `outEdit` khi click | `[hasEdit]="true"` |
378
+ | `[menuDropDownConfigs]` | `IDropdown` | `undefined` | Cấu hình menu dropdown "ba chấm dọc" ở góc phải header | `[menuDropDownConfigs]="menuConfig"` |
379
+ | `[zIndex]` | `number` | `1000` | CSS z-index của toàn bộ overlay | `[zIndex]="9999"` |
380
+ | `[ignoreClosePageFullEvent]` | `boolean` | `false` | Nếu `true`, không gửi `PostMessage` open/close tới app cha | `[ignoreClosePageFullEvent]="true"` |
381
+ | `[ignoreBackgroundColor]` | `boolean` | `undefined` | Nếu `true`, nền trong suốt thay vì màu mặc định `#f2f5f7` | `[ignoreBackgroundColor]="true"` |
382
+ | `[disable]` | `boolean` | `false` | Vô hiệu hóa toàn bộ action trên header (button, switch) | `[disable]="isLoading()"` |
383
+ | `[classHeaderInclude]` | `string` | `undefined` | Class bổ sung cho phần tử header | `[classHeaderInclude]="'border-b'"` |
384
+ | `[classBodyInclude]` | `string` | `undefined` | Class bổ sung cho phần body | `[classBodyInclude]="'p-[24px]'"` |
385
+ | `[leftTemplate]` | `TemplateRef<any>` | `undefined` | Template tùy chỉnh thay thế toàn bộ vùng trái header (bao gồm nút Back) | `[leftTemplate]="leftTpl"` |
386
+ | `[centerTemplate]` | `TemplateRef<any>` | `undefined` | Template tùy chỉnh thay thế vùng giữa header (bao gồm title + buttonCenter) | `[centerTemplate]="centerTpl"` |
387
+ | `[templateRight]` | `TemplateRef<any>` | `undefined` | Template bổ sung vào sau danh sách buttonRight ở vùng phải header | `[templateRight]="rightTpl"` |
388
+ | `[divideClassHeader]` | `{ classButtonCancel: string; classButtonCenter: string; classButtonRight: string }` | `undefined` | Override class width cho 3 vùng header (mặc định: 25% / 40% / 35%) | `[divideClassHeader]="{ classButtonCancel: 'w-[20%]', classButtonCenter: 'w-[50%]', classButtonRight: 'w-[30%]' }"` |
389
+
390
+ ---
391
+
392
+ ## @Input() — V2 (`libs_ui-components-pages_template_full_screen_v2`)
393
+
394
+ V2 kế thừa toàn bộ inputs của V1, bổ sung thêm:
395
+
396
+ | Input | Type | Default | Mô tả | Ví dụ |
397
+ |---|---|---|---|---|
398
+ | `[bodyConfig]` | `IFullScreenV2BodyConfig` | `{}` | Cấu hình lazy load component vào body. Nếu không truyền `getComponentOutlet` thì body render bằng `ng-content` như V1 | `[bodyConfig]="bodyConfig"` |
399
+
400
+ ---
401
+
402
+ ## @Output()
403
+
404
+ Cả V1 và V2 đều có chung các outputs sau:
405
+
406
+ | Output | Type | Mô tả | Handler TS | Binding HTML |
407
+ |---|---|---|---|---|
408
+ | `(outClose)` | `void` | Phát ra khi click nút quay lại / đóng ở vùng trái header | `handlerClose(): void { /* xử lý đóng */ }` | `(outClose)="handlerClose()"` |
409
+ | `(outEdit)` | `void` | Phát ra khi click icon edit cạnh tiêu đề (chỉ khi `[hasEdit]="true"`) | `handlerEdit(): void { /* mở form chỉnh sửa tiêu đề */ }` | `(outEdit)="handlerEdit()"` |
410
+ | `(outClickButton)` | `IPagesTemplateFullScreenButton` | Phát ra khi click bất kỳ button nào trong `buttonCenter` hoặc `buttonRight` có `key: 'button'` | `handlerClickButton(button: IPagesTemplateFullScreenButton): void { event.stopPropagation(); /* xử lý theo button.label */ }` | `(outClickButton)="handlerClickButton($event)"` |
411
+ | `(outSelectedMenuDropdown)` | `IEmitSelectKey \| undefined` | Phát ra khi chọn item trong menu dropdown (ba chấm dọc) | `handlerMenuSelect(event: IEmitSelectKey \| undefined): void { event.stopPropagation ? event.stopPropagation() : null; /* xử lý theo event.key */ }` | `(outSelectedMenuDropdown)="handlerMenuSelect($event)"` |
412
+
413
+ ---
414
+
415
+ ## FunctionsControl (V2 only)
416
+
417
+ `LibsUiComponentsPagesTemplateFullScreenV2Component` expose getter `FunctionsControl` để component con hoặc service bên ngoài có thể điều khiển trang:
111
418
 
112
419
  ```typescript
113
- export type IPagesTemplateFullScreenButtonKey = 'button' | 'button-status' | 'switch' | 'label' | 'line-space' | 'button-dropdown';
420
+ import { ComponentRef } from '@angular/core';
421
+ import {
422
+ IFunctionControlPageFullScreen,
423
+ LibsUiComponentsPagesTemplateFullScreenV2Component,
424
+ } from '@libs-ui/components-pages-template-full-screen';
425
+
426
+ // Lấy FunctionsControl từ ComponentRef
427
+ const fc: IFunctionControlPageFullScreen = fullScreenRef.instance.FunctionsControl;
428
+
429
+ // Gửi sự kiện đóng trang (PostMessage 'close') tới app cha
430
+ fc.emitClosePageFullEvent();
431
+ ```
114
432
 
433
+ | Method | Signature | Mô tả |
434
+ |---|---|---|
435
+ | `emitClosePageFullEvent` | `() => void` | Gửi `PostMessage` với `message: 'close'` tới ứng dụng cha qua `UtilsCommunicateMicro`. Thường dùng khi component con muốn chủ động đóng page mà không thông qua `(outClose)`. |
436
+
437
+ ---
438
+
439
+ ## Types & Interfaces
440
+
441
+ ```typescript
442
+ import {
443
+ IPagesTemplateFullScreenButton,
444
+ IPagesTemplateFullScreenButtonKey,
445
+ IFullScreenV2BodyConfig,
446
+ IFullScreenV2SectionData,
447
+ IFunctionControlPageFullScreen,
448
+ } from '@libs-ui/components-pages-template-full-screen';
449
+
450
+ // Loại button có thể dùng trong buttonCenter và buttonRight
451
+ export type IPagesTemplateFullScreenButtonKey =
452
+ | 'button' // Nút bấm thông thường
453
+ | 'button-status' // Nút hiển thị trạng thái (không click)
454
+ | 'switch' // Toggle switch
455
+ | 'label' // Label text
456
+ | 'line-space' // Đường kẻ phân cách dọc
457
+ | 'button-dropdown';// Nút dropdown
458
+
459
+ // Cấu hình mỗi button trong header
115
460
  export interface IPagesTemplateFullScreenButton extends IButton {
116
461
  key: IPagesTemplateFullScreenButtonKey;
117
462
  classInclude?: string;
118
463
  disable?: boolean;
464
+ // Async callback khi click — dùng cho switch (nhận ISwitchEvent) hoặc button
119
465
  action?: (event?: ISwitchEvent) => Promise<void>;
120
- configButtonStatus?: IButtonStatus;
121
- active?: boolean;
122
- labelConfig?: ILabel;
466
+ configButtonStatus?: IButtonStatus; // Chỉ dùng khi key = 'button-status'
467
+ active?: boolean; // Trạng thái ban đầu của switch
468
+ labelConfig?: ILabel; // Chỉ dùng khi key = 'label'
469
+ }
470
+
471
+ // Cấu hình body cho V2
472
+ export interface IFullScreenV2BodyConfig {
473
+ // Hàm trả về Observable<Type<any>> — lazy load component vào body
474
+ getComponentOutlet?: TYPE_FUNCTION<any>;
475
+ // Hàm trả về Observable<Record> — truyền inputs vào component được load
476
+ // Nhận sectionData (chứa { disable: boolean }) làm tham số
477
+ getDataComponentOutlet?: TYPE_FUNCTION<Record<string, unknown>>;
478
+ // Custom skeleton (bắt buộc là WritableSignal để reactive)
479
+ skeletonConfig?: WritableSignal<ISkeletonConfig>;
480
+ // Class bổ sung vào wrapper của vùng body
481
+ classInclude?: string;
482
+ }
483
+
484
+ // Data được truyền tự động vào component outlet (qua sectionData)
485
+ export interface IFullScreenV2SectionData {
486
+ disable: boolean;
487
+ }
488
+
489
+ // API điều khiển nội bộ
490
+ export interface IFunctionControlPageFullScreen {
491
+ emitClosePageFullEvent: () => void;
123
492
  }
124
493
  ```
125
494
 
126
- ## Công nghệ
495
+ ---
496
+
497
+ ## Lưu ý quan trọng
498
+
499
+ ⚠️ **V1 đã deprecated**: `LibsUiComponentsPagesTemplateFullScreenComponent` có annotation `@deprecated`. Chỉ dùng để maintain code cũ. Code mới bắt buộc dùng V2.
500
+
501
+ ⚠️ **PostMessage lifecycle**: Mặc định cả V1 và V2 đều gửi `PostMessage` với `message: 'open'` khi `ngOnInit` và `message: 'close'` khi `ngOnDestroy` tới ứng dụng cha. Nếu không cần hành vi này (ví dụ: dùng trong iframe độc lập hoặc demo), hãy set `[ignoreClosePageFullEvent]="true"`.
502
+
503
+ ⚠️ **Ping định kỳ 2 giây**: Nếu `IGNORE_INTERVAL_UPDATE_TIME_LIVE_EVENT_MODAL` là `false`, component gửi ping `PostMessage` mỗi 2 giây để báo cho app cha rằng user đang hoạt động. V2 dùng `takeUntilDestroyed` nên tự cleanup; V1 dùng `Subject` + `takeUntil`.
504
+
505
+ ⚠️ **ComponentRef phải là class property**: Khi dùng `dynamicService.resolveComponentFactory()`, BẮT BUỘC lưu kết quả vào biến class (không phải `const` trong hàm) để có thể `delete` khi component cha bị destroy. Quên xóa sẽ gây memory leak.
506
+
507
+ ⚠️ **skeletonConfig phải là WritableSignal**: Trường `bodyConfig.skeletonConfig` bắt buộc là `signal<ISkeletonConfig>(...)` (không phải plain object) để skeleton cập nhật reactive.
508
+
509
+ ⚠️ **getDataComponentOutlet nhận sectionData**: Hàm `getDataComponentOutlet` nhận tham số `{ item: IFullScreenV2SectionData }` (wrapper) — truy cập `sectionData.item.disable` để lấy trạng thái disable. Xem ví dụ 3 ở trên.
510
+
511
+ ⚠️ **Body layout**: Khi không truyền `bodyConfig.getComponentOutlet`, V2 không render gì ở body (không có `ng-content` như V1). Nếu cần `ng-content`, dùng V1 hoặc luôn truyền `getComponentOutlet`.
127
512
 
128
- | Technology | Version | Purpose |
129
- | --------------- | ------- | ---------------- |
130
- | Angular | 18+ | Framework |
131
- | Angular Signals | - | State management |
132
- | TailwindCSS | 3.x | Styling |
133
- | OnPush | - | Change Detection |
513
+ ---
134
514
 
135
515
  ## Demo
136
516
 
@@ -138,4 +518,6 @@ export interface IPagesTemplateFullScreenButton extends IButton {
138
518
  npx nx serve core-ui
139
519
  ```
140
520
 
141
- Truy cập: `http://localhost:4200/page-template/full-screen`
521
+ Truy cập:
522
+ - V1: `http://localhost:4500/page-template/full-screen`
523
+ - V2: `http://localhost:4500/page-template/full-screen-v2`