@libs-ui/interfaces-types 0.2.356-41 → 0.2.356-43

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.
Files changed (2) hide show
  1. package/README.md +617 -2
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,618 @@
1
- # interfaces-types
1
+ # @libs-ui/interfaces-types
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
3
+ > Bộ sưu tập tập trung các Interface và Type TypeScript dùng chung toàn hệ thống libs-ui.
4
+
5
+ ## Giới thiệu
6
+
7
+ `@libs-ui/interfaces-types` là thư viện thuần TypeScript cung cấp các interface và type được tái sử dụng xuyên suốt hệ sinh thái `@libs-ui`. Lib bao gồm các định nghĩa cho HTTP response/upload, phân trang, file, sự kiện trình duyệt, ngày tháng (dayjs), cũng như các utility type nâng cao phục vụ Angular Signals như `ExtractInputs`, `PathOf`, `GetValueAtPath`, và `SignalOf`.
8
+
9
+ ## Tính năng
10
+
11
+ - ✅ Định nghĩa chuẩn cho HTTP Response và phân trang (`IHttpResponse`, `IPaging`)
12
+ - ✅ Utility type trích xuất Input Signal từ Angular component (`ExtractInputs`)
13
+ - ✅ Type-safe path navigation cho object/signal lồng nhau (`PathOf`, `GetValueAtPath`)
14
+ - ✅ Chuyển đổi kiểu dữ liệu sang dạng Signal và ngược lại (`SignalOf`, `NonSignalOf`)
15
+ - ✅ Định nghĩa cho sự kiện trình duyệt mở rộng (`IEvent`, `IEventTarget`)
16
+ - ✅ Interface quản lý file upload và tiến trình (`IFile`, `IHttpProcessUpload`)
17
+ - ✅ Type ngày tháng với dayjs tích hợp (`GetDayjsConfig`, `GetDayjsReturn`)
18
+ - ✅ Interface validator và định dạng chuỗi (`IIsValidRequired`, `ITextFormatOptions`)
19
+ - ✅ Type tỉ lệ khung hình và toạ độ DOM (`TYPE_ASPECT_RATIO`, `IBoundingClientRect`)
20
+
21
+ ## Khi nào sử dụng
22
+
23
+ - Khi cần định nghĩa kiểu cho response API trả về từ backend
24
+ - Khi truyền data vào Modal V2 qua `of<ExtractInputs<...>>()` để đảm bảo type-safe
25
+ - Khi xây dựng hàm generic truy cập giá trị theo đường dẫn chuỗi trên object/signal lồng nhau
26
+ - Khi cần chuyển đổi một interface sang dạng Signal-wrapped để dùng với Angular Signals
27
+ - Khi xây dựng form validator và cần type chuẩn cho message lỗi
28
+ - Khi xử lý file upload và cần theo dõi tiến trình phần trăm
29
+
30
+ ## Cài đặt
31
+
32
+ ```bash
33
+ npm install @libs-ui/interfaces-types
34
+ ```
35
+
36
+ ## Import
37
+
38
+ ```typescript
39
+ // HTTP & Paging
40
+ import { IHttpResponse, IHttpResponseError, IPaging } from '@libs-ui/interfaces-types';
41
+
42
+ // File & Upload
43
+ import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';
44
+
45
+ // Angular Signal Utilities
46
+ import { ExtractInputs } from '@libs-ui/interfaces-types';
47
+ import { PathOf, GetValueAtPath, GetReturnType, SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';
48
+
49
+ // Event browser
50
+ import { IEvent, IEventTarget, IEventCustom } from '@libs-ui/interfaces-types';
51
+
52
+ // Date (dayjs)
53
+ import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';
54
+
55
+ // DOM
56
+ import { IBoundingClientRect } from '@libs-ui/interfaces-types';
57
+ import { TYPE_ASPECT_RATIO, IAspectRatio } from '@libs-ui/interfaces-types';
58
+
59
+ // Validator
60
+ import { IIsValidRequired, IIsValidPattern, IIsValidLength, IMessageTranslate } from '@libs-ui/interfaces-types';
61
+
62
+ // String
63
+ import { IString, IStringCustom, ITextFormatOptions } from '@libs-ui/interfaces-types';
64
+
65
+ // Others
66
+ import { TYPE_OBJECT } from '@libs-ui/interfaces-types';
67
+ import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
68
+ import { TYPE_FUNCTION } from '@libs-ui/interfaces-types';
69
+ import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';
70
+ import { TYPE_TEMPLATE_REF } from '@libs-ui/interfaces-types';
71
+ ```
72
+
73
+ ## Ví dụ sử dụng
74
+
75
+ ### 1. IHttpResponse — Định nghĩa kiểu trả về từ API
76
+
77
+ ```typescript
78
+ import { IHttpResponse, IPaging } from '@libs-ui/interfaces-types';
79
+ import { HttpClient } from '@angular/common/http';
80
+ import { inject } from '@angular/core';
81
+ import { Observable } from 'rxjs';
82
+
83
+ interface IUser {
84
+ id: string;
85
+ name: string;
86
+ email: string;
87
+ }
88
+
89
+ // Service gọi API danh sách user có phân trang
90
+ export class UserApiService {
91
+ private readonly http = inject(HttpClient);
92
+
93
+ getList(params: { page: number; per_page: number }): Observable<IHttpResponse<IUser[]>> {
94
+ return this.http.get<IHttpResponse<IUser[]>>('/api/users', { params });
95
+ }
96
+ }
97
+
98
+ // Component nhận response
99
+ // component.ts
100
+ protected handlerLoadData(): void {
101
+ this.userApiService.getList({ page: 1, per_page: 20 })
102
+ .pipe(takeUntilDestroyed(this.destroyRef))
103
+ .subscribe((res: IHttpResponse<IUser[]>) => {
104
+ if (res.code !== 200) return;
105
+ this.users.set(res.data ?? []);
106
+ this.paging.set(res.paging ?? {});
107
+ });
108
+ }
109
+ ```
110
+
111
+ ### 2. ExtractInputs — Truyền data type-safe vào Modal V2
112
+
113
+ `ExtractInputs<T>` trích xuất tất cả `InputSignal` từ một component thành một plain object type, dùng khi gọi `of<ExtractInputs<...>>()` để truyền data vào `getDataComponentOutlet`.
114
+
115
+ ```typescript
116
+ import { ExtractInputs } from '@libs-ui/interfaces-types';
117
+ import { InputSignal, input } from '@angular/core';
118
+ import { of } from 'rxjs';
119
+ import { from } from 'rxjs';
120
+ import { LibsUiComponentsModalV2Component } from '@libs-ui/components-modal-v2';
121
+
122
+ // Định nghĩa interface khớp với component con
123
+ interface I_DataInComponentUserDetail {
124
+ userId: InputSignal<string>; // required
125
+ mode: InputSignal<'view' | 'edit'>; // required
126
+ title?: InputSignal<string>; // optional
127
+ }
128
+
129
+ // Component con implements interface
130
+ export class UserDetailModalComponent implements I_DataInComponentUserDetail {
131
+ readonly userId = input.required<string>();
132
+ readonly mode = input.required<'view' | 'edit'>();
133
+ readonly title = input<string>();
134
+ }
135
+
136
+ // Component cha truyền data qua getDataComponentOutlet
137
+ this.libsUiDynamicComponentService.setInputs(this.modalComponentRef, {
138
+ bodyConfig: {
139
+ component: () => from(
140
+ import('./user-detail-modal/user-detail-modal.component')
141
+ .then(m => m.UserDetailModalComponent)
142
+ ),
143
+ getDataComponentOutlet: () => of<ExtractInputs<I_DataInComponentUserDetail>>({
144
+ userId: 'user-123', // required — bắt buộc truyền
145
+ mode: 'view', // required — bắt buộc truyền
146
+ // title: 'Chi tiết', // optional — có thể bỏ qua
147
+ }),
148
+ },
149
+ });
150
+ ```
151
+
152
+ ### 3. PathOf & GetValueAtPath — Truy cập an toàn theo đường dẫn chuỗi
153
+
154
+ `PathOf<T>` sinh ra union type của tất cả đường dẫn hợp lệ trong object `T`. `GetValueAtPath<T, P>` trả về type của giá trị tại đường dẫn `P`.
155
+
156
+ ```typescript
157
+ import { PathOf, GetValueAtPath } from '@libs-ui/interfaces-types';
158
+ import { WritableSignal, signal } from '@angular/core';
159
+
160
+ interface IOrder {
161
+ id: string;
162
+ customer: {
163
+ name: string;
164
+ address: {
165
+ city: string;
166
+ zip: string;
167
+ };
168
+ };
169
+ items: Array<{
170
+ productId: string;
171
+ quantity: number;
172
+ }>;
173
+ }
174
+
175
+ // PathOf<IOrder> tự động sinh ra:
176
+ // 'id' | 'customer' | 'customer.name' | 'customer.address' |
177
+ // 'customer.address.city' | 'customer.address.zip' |
178
+ // 'items' | 'items[0]' | 'items[0].productId' | 'items[0].quantity' | ...
179
+ type OrderPaths = PathOf<IOrder>;
180
+
181
+ // GetValueAtPath trả về type chính xác tại path
182
+ type CityType = GetValueAtPath<IOrder, 'customer.address.city'>; // string
183
+ type ItemQty = GetValueAtPath<IOrder, 'items[0].quantity'>; // number
184
+
185
+ // Dùng trong hàm generic type-safe
186
+ function getField<T, P extends PathOf<T>>(
187
+ obj: T,
188
+ path: P
189
+ ): GetValueAtPath<T, P> {
190
+ // implementation dùng get() từ @libs-ui/utils
191
+ return (obj as any)[path as string];
192
+ }
193
+ ```
194
+
195
+ ### 4. SignalOf — Chuyển đổi interface sang Signal-wrapped
196
+
197
+ ```typescript
198
+ import { SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';
199
+ import { signal, WritableSignal } from '@angular/core';
200
+ import { convertObjectToSignal, convertSignalToObject } from '@libs-ui/utils';
201
+
202
+ interface IUserProfile {
203
+ name: string;
204
+ age: number;
205
+ address: {
206
+ city: string;
207
+ country: string;
208
+ };
209
+ tags: string[];
210
+ }
211
+
212
+ // SignalOf<IUserProfile> sẽ sinh ra:
213
+ // {
214
+ // name: WritableSignal<string>;
215
+ // age: WritableSignal<number>;
216
+ // address: WritableSignal<{ city: WritableSignal<string>; country: WritableSignal<string>; }>;
217
+ // tags: WritableSignal<WritableSignal<string>[]>;
218
+ // }
219
+ type SignaledProfile = SignalOf<IUserProfile>;
220
+
221
+ // Sử dụng với convertObjectToSignal từ @libs-ui/utils
222
+ const profileSignal = convertObjectToSignal<IUserProfile>({
223
+ name: 'Nguyễn Văn A',
224
+ age: 30,
225
+ address: { city: 'Hà Nội', country: 'Việt Nam' },
226
+ tags: ['admin', 'dev'],
227
+ });
228
+
229
+ // Chuyển ngược lại về plain object để gửi API
230
+ const payload = convertSignalToObject(profileSignal);
231
+ ```
232
+
233
+ ### 5. IFile & IHttpProcessUpload — Quản lý file upload
234
+
235
+ ```typescript
236
+ import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';
237
+ import { signal } from '@angular/core';
238
+
239
+ // component.ts
240
+ protected uploadingFiles = signal<IFile[]>([]);
241
+ protected uploadProgress = signal<IHttpProcessUpload>({ loaded: 0, total: 0, percent: 0 });
242
+
243
+ protected handlerFileSelect(event: Event): void {
244
+ event.stopPropagation();
245
+ const input = event.target as HTMLInputElement;
246
+ if (!input.files?.length) return;
247
+
248
+ const newFiles: IFile[] = Array.from(input.files).map(file => ({
249
+ name: file.name,
250
+ file: file,
251
+ size: `${(file.size / 1024).toFixed(1)} KB`,
252
+ type: this.detectFileType(file.type),
253
+ isUploading: true,
254
+ percentUploading: 0,
255
+ }));
256
+
257
+ this.uploadingFiles.set(newFiles);
258
+ }
259
+
260
+ private detectFileType(mimeType: string): TYPE_FILE {
261
+ if (mimeType.startsWith('image/')) return 'image';
262
+ if (mimeType.startsWith('video/')) return 'video';
263
+ if (mimeType.startsWith('audio/')) return 'audio';
264
+ return 'document';
265
+ }
266
+ ```
267
+
268
+ ### 6. IEvent & IEventTarget — Xử lý sự kiện DOM mở rộng
269
+
270
+ ```typescript
271
+ import { IEvent } from '@libs-ui/interfaces-types';
272
+
273
+ // component.ts
274
+ protected handlerInputChange(event: IEvent): void {
275
+ event.stopPropagation();
276
+ const value = event.target.value as string;
277
+ this.searchTerm.set(value.trim());
278
+ }
279
+
280
+ protected handlerFileDrop(event: IEvent): void {
281
+ event.stopPropagation();
282
+ const files = event.dataTransfer?.files;
283
+ if (!files?.length) return;
284
+ this.processFiles(files);
285
+ }
286
+
287
+ protected handlerKeyDown(event: IEvent): void {
288
+ event.stopPropagation();
289
+ if (event.keyCode === 13) {
290
+ this.handlerSubmit();
291
+ return;
292
+ }
293
+ if (event.keyCode === 27) {
294
+ this.handlerClose();
295
+ }
296
+ }
297
+ ```
298
+
299
+ ### 7. GetDayjsConfig & GetDayjsReturn — Type cho hàm getDayjs
300
+
301
+ ```typescript
302
+ import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';
303
+ import { getDayjs } from '@libs-ui/utils';
304
+
305
+ // Định nghĩa hàm format ngày với type rõ ràng
306
+ function formatDateSafe(config: GetDayjsConfig): string {
307
+ const result = getDayjs(config);
308
+ if (!result) return '';
309
+ return result.format('DD/MM/YYYY HH:mm');
310
+ }
311
+
312
+ // Ví dụ sử dụng
313
+ const formatted1 = formatDateSafe({ date: '2024-06-15', formatOfDate: 'YYYY-MM-DD' });
314
+ // => '15/06/2024 00:00'
315
+
316
+ const formatted2 = formatDateSafe({
317
+ date: 1718409600000,
318
+ utc: false,
319
+ returnDayjsIfConfigDateNotExist: true,
320
+ });
321
+ ```
322
+
323
+ ### 8. IPaging — Phân trang
324
+
325
+ ```typescript
326
+ import { IPaging, IHttpResponse } from '@libs-ui/interfaces-types';
327
+ import { signal, computed } from '@angular/core';
328
+
329
+ // component.ts
330
+ protected paging = signal<IPaging>({});
331
+
332
+ protected totalPages = computed(() => this.paging().total_pages ?? 0);
333
+ protected currentPage = computed(() => this.paging().page ?? 1);
334
+ protected hasNextPage = computed(() => !!this.paging().next);
335
+
336
+ protected handlerPageChange(page: number): void {
337
+ this.loadData({ page, per_page: 20 });
338
+ }
339
+
340
+ private loadData(params: { page: number; per_page: number }): void {
341
+ this.apiService.getList(params)
342
+ .pipe(takeUntilDestroyed(this.destroyRef))
343
+ .subscribe((res: IHttpResponse<unknown[], IPaging>) => {
344
+ if (res.code !== 200) return;
345
+ this.paging.set(res.paging ?? {});
346
+ });
347
+ }
348
+ ```
349
+
350
+ ### 9. Validator interfaces — Cấu hình rule validate
351
+
352
+ ```typescript
353
+ import {
354
+ IIsValidRequired,
355
+ IIsValidPattern,
356
+ IIsValidLength,
357
+ IMessageTranslate,
358
+ } from '@libs-ui/interfaces-types';
359
+
360
+ // Cấu hình validator cho input email
361
+ const emailValidators = {
362
+ required: {
363
+ isRequired: true,
364
+ message: 'i18n_email_required',
365
+ } satisfies IIsValidRequired,
366
+
367
+ pattern: {
368
+ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
369
+ message: 'i18n_email_invalid',
370
+ valuePatternShowError: true,
371
+ } satisfies IIsValidPattern,
372
+
373
+ maxLength: {
374
+ length: 100,
375
+ message: 'i18n_email_too_long',
376
+ interpolateParams: { max: 100 },
377
+ } satisfies IIsValidLength,
378
+ };
379
+ ```
380
+
381
+ ### 10. TYPE_LANGUAGE_SUPPORT — Kiểu ngôn ngữ được hỗ trợ
382
+
383
+ ```typescript
384
+ import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
385
+ import { signal } from '@angular/core';
386
+
387
+ // component.ts
388
+ protected currentLang = signal<TYPE_LANGUAGE_SUPPORT>('vi');
389
+
390
+ protected handlerSwitchLang(lang: TYPE_LANGUAGE_SUPPORT): void {
391
+ this.currentLang.set(lang);
392
+ this.translateService.use(lang);
393
+ }
394
+ ```
395
+
396
+ ### 11. IAspectRatio & TYPE_ASPECT_RATIO — Tỉ lệ khung hình
397
+
398
+ ```typescript
399
+ import { IAspectRatio, TYPE_ASPECT_RATIO } from '@libs-ui/interfaces-types';
400
+ import { signal } from '@angular/core';
401
+
402
+ const aspectRatioOptions: IAspectRatio[] = [
403
+ { key: 'Tự do', value: 0 },
404
+ { key: '1:1', value: 1 },
405
+ { key: '16:9', value: 16 / 9 },
406
+ { key: '4:3', value: 4 / 3 },
407
+ ];
408
+
409
+ // component.ts
410
+ protected selectedRatio = signal<TYPE_ASPECT_RATIO>('16:9');
411
+
412
+ protected handlerSelectRatio(ratio: TYPE_ASPECT_RATIO): void {
413
+ this.selectedRatio.set(ratio);
414
+ }
415
+ ```
416
+
417
+ ### 12. IBoundingClientRect — Toạ độ và kích thước DOM
418
+
419
+ ```typescript
420
+ import { IBoundingClientRect } from '@libs-ui/interfaces-types';
421
+ import { ElementRef, inject } from '@angular/core';
422
+ import { signal } from '@angular/core';
423
+
424
+ // component.ts (dùng trong directive hoặc component tính toán vị trí)
425
+ private readonly elementRef = inject(ElementRef);
426
+
427
+ protected boundingRect = signal<IBoundingClientRect>({
428
+ top: 0, left: 0, width: 0, height: 0,
429
+ });
430
+
431
+ ngAfterViewInit(): void {
432
+ const rect = this.elementRef.nativeElement.getBoundingClientRect();
433
+ this.boundingRect.set({
434
+ top: rect.top,
435
+ left: rect.left,
436
+ width: rect.width,
437
+ height: rect.height,
438
+ right: rect.right,
439
+ bottom: rect.bottom,
440
+ });
441
+ }
442
+ ```
443
+
444
+ ### 13. TYPE_IFRAME_MESSAGE — Giao tiếp giữa các micro-frontend
445
+
446
+ ```typescript
447
+ import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';
448
+
449
+ type MessageType = 'AUTH_TOKEN' | 'PAGE_READY' | 'CLOSE_MODAL';
450
+ type MessagePayload = { token: string } | { route: string } | null;
451
+
452
+ type AppMessage = TYPE_IFRAME_MESSAGE<MessageType, MessagePayload>;
453
+
454
+ // Gửi message
455
+ const msg: AppMessage = { type: 'AUTH_TOKEN', response: { token: 'demo-token' } };
456
+ window.parent.postMessage(msg, '*');
457
+
458
+ // Nhận message
459
+ window.addEventListener('message', (event) => {
460
+ const data = event.data as AppMessage;
461
+ if (data.type === 'AUTH_TOKEN') {
462
+ // xử lý token
463
+ }
464
+ });
465
+ ```
466
+
467
+ ## Types & Interfaces
468
+
469
+ ### HTTP
470
+
471
+ | Export | Loại | Mô tả |
472
+ |---|---|---|
473
+ | `IHttpResponse<T, P>` | type | Kiểu chuẩn cho HTTP response từ backend. `T` = data type, `P` = paging type |
474
+ | `IHttpResponseError` | interface | Lỗi HTTP mở rộng từ `Error`, có thêm `code` |
475
+ | `IPaging` | interface | Phân trang hỗ trợ cả cursor-based và offset-based |
476
+ | `IHttpProcessUpload` | interface | Tiến trình upload: `loaded`, `total`, `percent` |
477
+
478
+ ```typescript
479
+ // IHttpResponse chi tiết
480
+ type IHttpResponse<T = any, P = IPaging> = {
481
+ code?: number; // HTTP status code
482
+ feCloneCode?: any; // Code override khi server trả 200 nhưng logic khác
483
+ message?: string; // Thông báo từ server
484
+ data?: T; // Dữ liệu chính
485
+ paging?: P; // Thông tin phân trang
486
+ [key: string]: unknown; // Các field mở rộng
487
+ };
488
+
489
+ // IPaging chi tiết
490
+ interface IPaging {
491
+ page?: number; // Trang hiện tại (offset-based)
492
+ per_page?: number; // Số item mỗi trang
493
+ total_items?: number; // Tổng số item
494
+ total_pages?: number; // Tổng số trang
495
+ before?: string; // Cursor trước (cursor-based)
496
+ after?: string; // Cursor sau (cursor-based)
497
+ previous?: string; // URL trang trước
498
+ next?: string; // URL trang sau
499
+ fake?: boolean; // Dữ liệu giả (dùng trong loading state)
500
+ }
501
+ ```
502
+
503
+ ### File
504
+
505
+ | Export | Loại | Mô tả |
506
+ |---|---|---|
507
+ | `IFile` | interface | Đối tượng file đầy đủ: id, name, url, type, upload progress |
508
+ | `TYPE_FILE` | type | `'document' \| 'image' \| 'video' \| 'audio'` |
509
+
510
+ ```typescript
511
+ interface IFile {
512
+ id?: string;
513
+ name?: string;
514
+ file?: File; // File object gốc từ input[type=file]
515
+ size?: string; // Chuỗi kích thước (vd: '2.5 MB')
516
+ isUploading?: boolean;
517
+ percentUploading?: number; // 0-100
518
+ isUpdate?: boolean; // Đang cập nhật file cũ
519
+ url?: string; // URL sau khi upload xong
520
+ origin_url?: string; // URL gốc
521
+ mimetype?: string; // MIME type (vd: 'image/png')
522
+ type?: TYPE_FILE;
523
+ error?: string;
524
+ isAvatar?: boolean;
525
+ }
526
+ ```
527
+
528
+ ### Angular Signal Utilities
529
+
530
+ | Export | Loại | Mô tả |
531
+ |---|---|---|
532
+ | `ExtractInputs<T, R>` | type | Trích xuất `InputSignal` / `ModelSignal` từ component thành plain object type. `R=true` (mặc định) giữ nguyên required/optional |
533
+ | `PathOf<T, Depth>` | type | Sinh union type tất cả đường dẫn hợp lệ trong object `T`, hỗ trợ signal lồng nhau, depth mặc định 6 |
534
+ | `GetValueAtPath<T, P, R>` | type | Resolve type giá trị tại path `P`. `R=false` (mặc định) unwrap signal, `R=true` giữ nguyên signal |
535
+ | `GetReturnType<O, P, KS, D>` | type | Kiểu trả về của `get()` dựa trên `defaultValue D` — loại `null \| undefined` nếu `D` có giá trị |
536
+ | `SignalOf<T, P, F>` | type | Chuyển toàn bộ object sang dạng Signal-wrapped. `P=true` (mặc định) wrap cả primitive |
537
+ | `NonSignalOf<T>` | type | Unwrap toàn bộ Signal về plain type |
538
+
539
+ ```typescript
540
+ // Ví dụ ExtractInputs
541
+ interface I_DataInComponentBadge {
542
+ label: InputSignal<string>; // required
543
+ variant: InputSignal<'primary' | 'secondary'>; // required
544
+ count?: InputSignal<number>; // optional (có ? vì InputSignal<number | undefined>)
545
+ }
546
+
547
+ type BadgeData = ExtractInputs<I_DataInComponentBadge>;
548
+ // => { label: string; variant: 'primary' | 'secondary'; count?: number }
549
+
550
+ // R=false: optional field có thể bỏ qua
551
+ type BadgeDataPartial = ExtractInputs<I_DataInComponentBadge, false>;
552
+ // => { label?: string; variant?: string; count?: number }
553
+ ```
554
+
555
+ ### Event Browser
556
+
557
+ | Export | Loại | Mô tả |
558
+ |---|---|---|
559
+ | `IEvent` | interface | Kết hợp `Event`, `MouseEvent`, `KeyboardEvent`, `ErrorEvent` và custom |
560
+ | `IEventTarget` | interface | `EventTarget` mở rộng với `value`, `files`, `style`, `innerHTML`,... |
561
+ | `IEventCustom` | interface | Event tuỳ chỉnh với `data`, `fakeEvent`, `inputType` |
562
+
563
+ ### Date
564
+
565
+ | Export | Loại | Mô tả |
566
+ |---|---|---|
567
+ | `GetDayjsConfig` | type | Config truyền vào `getDayjs()`: `date`, `utc`, `formatOfDate`, `returnDayjsIfConfigDateNotExist`, `localeZone` |
568
+ | `GetDayjsReturn<C>` | type | Kiểu trả về điều kiện của `getDayjs()`: `dayjs.Dayjs` hoặc `undefined` tùy config |
569
+ | `NonNullableDate` | type | `dayjs.ConfigType` loại bỏ `null \| undefined` |
570
+
571
+ ### DOM & Layout
572
+
573
+ | Export | Loại | Mô tả |
574
+ |---|---|---|
575
+ | `IBoundingClientRect` | interface | `top`, `left`, `width`, `height`, `right?`, `bottom?` |
576
+ | `IAspectRatio` | interface | `key: string`, `value: number` |
577
+ | `TYPE_ASPECT_RATIO` | type | `'free' \| '1:1' \| '2:3' \| '3:2' \| '3:4' \| '4:3' \| '9:16' \| '16:9'` |
578
+
579
+ ### Validator
580
+
581
+ | Export | Loại | Mô tả |
582
+ |---|---|---|
583
+ | `IIsValidRequired` | interface | Validator required: `isRequired`, `message`, `interpolateParams` |
584
+ | `IIsValidPattern` | interface | Validator regex: `pattern`, `valuePatternShowError`, `message` |
585
+ | `IIsValidLength` | interface | Validator độ dài: `length`, `message`, `interpolateParams` |
586
+ | `IMessageTranslate` | interface | Base interface cho message lỗi: `message?`, `interpolateParams?` |
587
+
588
+ ### String
589
+
590
+ | Export | Loại | Mô tả |
591
+ |---|---|---|
592
+ | `IString` | type | `String & string & IStringCustom` — chuỗi mở rộng |
593
+ | `IStringCustom` | interface | Thêm `replaceAt`, `replaceAll`, `occurrencesByCharacter`, `indexesOfCharacter`, `indexesByString` |
594
+ | `ITextFormatOptions` | interface | Options format text: `uppercaseOtherCharacter`, `lowercaseOtherCharacter`, `trim`, `removeMultipleSpace`, `removeEmoji`, `removeUnicode` |
595
+
596
+ ### Khác
597
+
598
+ | Export | Loại | Mô tả |
599
+ |---|---|---|
600
+ | `TYPE_OBJECT` | type | `Record<any, any>` — object không định kiểu. Chỉ dùng khi có lý do, phải kèm comment `// CONVENTION-EXCEPT: TYPE_OBJECT — [lý do]` |
601
+ | `TYPE_LANGUAGE_SUPPORT` | type | `'vi' \| 'en'` — ngôn ngữ được hỗ trợ hiện tại |
602
+ | `TYPE_FUNCTION<T>` | type | `(data: { value: any; item?: any; otherData?: any }) => Observable<T>` |
603
+ | `TYPE_IFRAME_MESSAGE<T, U>` | type | `{ type: T; response: U }` — message giao tiếp giữa micro-frontend qua iframe |
604
+ | `TYPE_TEMPLATE_REF` | type | `any` — placeholder type cho `TemplateRef` chưa xác định |
605
+
606
+ ## Lưu ý quan trọng
607
+
608
+ ⚠️ **ExtractInputs yêu cầu Generic Type tường minh**: Khi truyền data qua `of()` vào `getDataComponentOutlet`, BẮT BUỘC khai báo `of<ExtractInputs<I_DataInComponent...>>({...})`. Dùng `of({...})` không có generic sẽ mất type-safety và TypeScript không báo lỗi thiếu field.
609
+
610
+ ⚠️ **PathOf có Depth giới hạn mặc định là 6**: Với object lồng nhau sâu hơn 6 cấp, các path vượt quá sẽ trả về `never`. Có thể tăng `Depth` khi cần: `PathOf<T, 8>`.
611
+
612
+ ⚠️ **SignalOf không wrap các kiểu đặc biệt thêm lần nữa**: `Dayjs`, `Date`, `File`, `Blob`, `HttpParams`, `FormData`, `Event`, `Node`, `Map`, `Set`, `RegExp` và các Promise/Function sẽ chỉ được wrap 1 cấp signal — không đệ quy vào bên trong.
613
+
614
+ ⚠️ **TYPE_OBJECT cần comment lý do**: Theo coding convention, mọi nơi sử dụng `TYPE_OBJECT` phải kèm comment `// CONVENTION-EXCEPT: TYPE_OBJECT — [lý do cụ thể]`. Không dùng tuỳ tiện.
615
+
616
+ ⚠️ **IHttpResponse.feCloneCode là trường đặc biệt**: Trường này chỉ được gán từ phía frontend khi muốn override logic sau khi server trả về HTTP 200 nhưng business code lại là giá trị khác. Không phải field từ backend.
617
+
618
+ ⚠️ **GetValueAtPath với R=false/true khác nhau**: Tham số `R=false` (mặc định) sẽ unwrap toàn bộ `WritableSignal` về giá trị thuần. `R=true` giữ nguyên cấu trúc signal. Lựa chọn `R` phải nhất quán với mục đích sử dụng.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libs-ui/interfaces-types",
3
- "version": "0.2.356-41",
3
+ "version": "0.2.356-43",
4
4
  "peerDependencies": {
5
5
  "rxjs": "~7.8.0",
6
6
  "dayjs": "1.11.5",