@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 +468 -86
- package/esm2022/full-screen-v2/full-screen-v2.component.mjs +24 -14
- package/esm2022/full-screen-v2/interfaces/full-screen-v2.interface.mjs +1 -1
- package/esm2022/full-screen.component.mjs +6 -3
- package/fesm2022/libs-ui-components-pages-template-full-screen.mjs +30 -17
- package/fesm2022/libs-ui-components-pages-template-full-screen.mjs.map +1 -1
- package/full-screen-v2/full-screen-v2.component.d.ts +7 -1
- package/full-screen-v2/interfaces/full-screen-v2.interface.d.ts +3 -0
- package/full-screen.component.d.ts +5 -2
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -1,136 +1,516 @@
|
|
|
1
1
|
# @libs-ui/components-pages-template-full-screen
|
|
2
2
|
|
|
3
|
-
> Khung layout
|
|
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
|
-
|
|
7
|
+
Package này cung cấp hai component layout toàn màn hình cho Angular:
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
12
|
+
Cả hai đều tích hợp sẵn cơ 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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
|
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
|
-
|
|
112
|
+
### Ví dụ 2: Header với switch, dropdown menu và edit icon
|
|
47
113
|
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
205
|
+
---
|
|
62
206
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
207
|
+
## Ví dụ sử dụng — V2 (khuyến dùng, mở động qua DynamicComponentService)
|
|
208
|
+
|
|
209
|
+
### Ví 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
521
|
+
Truy cập:
|
|
522
|
+
- V1: `http://localhost:4500/page-template/full-screen`
|
|
523
|
+
- V2: `http://localhost:4500/page-template/full-screen-v2`
|