@ldquocc/dynamic-form 0.0.6 → 0.0.8

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 (45) hide show
  1. package/fesm2022/ldquocc-dynamic-form.mjs +1013 -716
  2. package/fesm2022/ldquocc-dynamic-form.mjs.map +1 -1
  3. package/lib/shared/base/base-select.component.d.ts +19 -0
  4. package/lib/shared/base/base-value-accessor.d.ts +17 -10
  5. package/lib/shared/components/component.d.ts +2 -1
  6. package/lib/shared/components/field-user-config/field-user-config.component.d.ts +10 -0
  7. package/lib/shared/components/fields/date/date-picker/date-picker.component.d.ts +6 -4
  8. package/lib/shared/components/fields/date/date-range/date-range.component.d.ts +5 -4
  9. package/lib/shared/components/fields/input/input-formatted/input-formatted.component.d.ts +20 -0
  10. package/lib/shared/components/fields/input/input-password/input-password.component.d.ts +4 -3
  11. package/lib/shared/components/fields/input/input-text/input-text.component.d.ts +5 -5
  12. package/lib/shared/components/fields/select/multi-select/multi-select.component.d.ts +7 -11
  13. package/lib/shared/components/fields/select/single-select/single-select.component.d.ts +7 -12
  14. package/lib/shared/components/fields/select/tree-select/tree-select.component.d.ts +4 -3
  15. package/lib/shared/components/fields/textarea/textarea.component.d.ts +5 -4
  16. package/lib/shared/components/fields/upload/avatar-upload/avatar-upload.component.d.ts +12 -14
  17. package/lib/shared/components/fields/upload/multi-file-upload/multi-file-upload.component.d.ts +2 -4
  18. package/lib/shared/components/fields/upload/multi-image-upload/multi-image-upload.component.d.ts +10 -11
  19. package/lib/shared/components/fields/upload/single-file-upload/single-file-upload.component.d.ts +15 -16
  20. package/lib/shared/consts/common/common.const.d.ts +4 -0
  21. package/lib/shared/consts/fields/field.const.d.ts +1 -1
  22. package/lib/shared/consts/fields/index.d.ts +5 -4
  23. package/lib/shared/consts/fields/input-formatted-field.const.d.ts +4 -0
  24. package/lib/shared/enums/fields/field.enum.d.ts +1 -0
  25. package/lib/shared/enums/fields/index.d.ts +6 -5
  26. package/lib/shared/enums/fields/input-formatted-field.enum.d.ts +6 -0
  27. package/lib/shared/enums/validators/index.d.ts +2 -1
  28. package/lib/shared/enums/validators/regex.enum.d.ts +38 -0
  29. package/lib/shared/factories/auto-numeric-options.factory.d.ts +5 -0
  30. package/lib/shared/models/common/index.d.ts +7 -7
  31. package/lib/shared/models/fields/date/date-range-field.model.d.ts +3 -2
  32. package/lib/shared/models/fields/field-grid.model.d.ts +7 -7
  33. package/lib/shared/models/fields/field-user-config.model.d.ts +3 -0
  34. package/lib/shared/models/fields/field.model.d.ts +9 -7
  35. package/lib/shared/models/fields/index.d.ts +2 -0
  36. package/lib/shared/models/fields/input/index.d.ts +4 -3
  37. package/lib/shared/models/fields/input/input-formatted-field.model.d.ts +13 -0
  38. package/lib/shared/models/fields/select/select-base-field.model.d.ts +6 -6
  39. package/lib/shared/models/fields/upload/multi-image-upload-field.model.d.ts +1 -1
  40. package/lib/shared/services/field.service.d.ts +17 -5
  41. package/lib/shared/utils/dynamic-form/dynamic-form.d.ts +8 -4
  42. package/package.json +3 -4
  43. package/public-api.d.ts +2 -2
  44. package/styles/_common-form.scss +43 -0
  45. package/lib/shared/models/fields/index.model.d.ts +0 -1
@@ -1,11 +1,11 @@
1
1
  import * as i1 from '@angular/forms';
2
2
  import { FormsModule, ReactiveFormsModule, Validators, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
3
- import { of, delay, Subject, takeUntil, finalize, Observable, debounceTime, distinctUntilChanged } from 'rxjs';
3
+ import { of, delay, Subject, BehaviorSubject, takeUntil, combineLatest, finalize, Observable, debounceTime, distinctUntilChanged } from 'rxjs';
4
4
  import * as i1$2 from '@angular/common';
5
5
  import { CommonModule, CurrencyPipe } from '@angular/common';
6
6
  import { RouterModule } from '@angular/router';
7
7
  import * as i0 from '@angular/core';
8
- import { InjectionToken, inject, Injectable, signal, untracked, input, computed, Input, Directive, effect, forwardRef, Component, EventEmitter, output, Output } from '@angular/core';
8
+ import { InjectionToken, inject, Injectable, signal, untracked, input, computed, effect, Input, Directive, forwardRef, Component, viewChild, EventEmitter, output, Output } from '@angular/core';
9
9
  import * as i3$3 from 'ng-zorro-antd/form';
10
10
  import { NzFormModule } from 'ng-zorro-antd/form';
11
11
  import { isEqual } from 'lodash-es';
@@ -15,13 +15,16 @@ import * as i2 from 'ng-zorro-antd/date-picker';
15
15
  import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
16
16
  import * as i1$1 from 'ng-zorro-antd/input';
17
17
  import { NzInputModule } from 'ng-zorro-antd/input';
18
- import * as i3 from 'ng-zorro-antd/icon';
19
- import { NzIconModule } from 'ng-zorro-antd/icon';
20
18
  import * as i4 from 'ng-zorro-antd/button';
21
19
  import { NzButtonModule } from 'ng-zorro-antd/button';
20
+ import * as i3 from 'ng-zorro-antd/icon';
21
+ import { NzIconModule } from 'ng-zorro-antd/icon';
22
22
  import * as i5 from 'ng-zorro-antd/core/transition-patch';
23
23
  import * as i6 from 'ng-zorro-antd/core/wave';
24
+ import AutoNumeric from '@zeedhi/autonumeric';
24
25
  import { NgxCurrencyDirective } from 'ngx-currency';
26
+ import { toObservable, toSignal, rxResource } from '@angular/core/rxjs-interop';
27
+ import { map, switchMap, catchError } from 'rxjs/operators';
25
28
  import * as i3$1 from 'ng-zorro-antd/select';
26
29
  import { NzSelectModule } from 'ng-zorro-antd/select';
27
30
  import * as i3$2 from 'ng-zorro-antd/spin';
@@ -31,14 +34,19 @@ import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select';
31
34
  import * as i2$2 from 'ng-zorro-antd/upload';
32
35
  import { NzUploadModule } from 'ng-zorro-antd/upload';
33
36
  import { NzNotificationService } from 'ng-zorro-antd/notification';
34
- import { ToastrService } from 'ngx-toastr';
35
- import { TranslateService } from '@ngx-translate/core';
36
37
  import { NzMessageService } from 'ng-zorro-antd/message';
37
38
  import * as i7 from 'ng-zorro-antd/image';
38
39
  import { NzImageModule } from 'ng-zorro-antd/image';
39
- import * as i2$3 from 'ng-zorro-antd/grid';
40
+ import * as i2$4 from 'ng-zorro-antd/grid';
40
41
  import { NzGridModule } from 'ng-zorro-antd/grid';
41
- import * as i4$1 from 'ng-zorro-antd/collapse';
42
+ import * as i2$3 from 'ng-zorro-antd/dropdown';
43
+ import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
44
+ import * as i4$1 from 'ng-zorro-antd/switch';
45
+ import { NzSwitchModule } from 'ng-zorro-antd/switch';
46
+ import * as i1$3 from 'ng-zorro-antd/menu';
47
+ import * as i4$2 from 'ng-zorro-antd/flex';
48
+ import { NzFlexModule } from 'ng-zorro-antd/flex';
49
+ import * as i4$3 from 'ng-zorro-antd/collapse';
42
50
  import { NzCollapseModule } from 'ng-zorro-antd/collapse';
43
51
 
44
52
  // Core Angular modules (cần thiết ở hầu hết các component).
@@ -50,7 +58,11 @@ const ROUTING_MODULES = [RouterModule];
50
58
  // 🧩 All common shared modules (nếu bạn muốn gộp nhanh)
51
59
  const SHARED_MODULES = [...CORE_MODULES, ...FORM_MODULES, ...ROUTING_MODULES];
52
60
 
53
- const NO_INFO_TEXT = 'Không có thông tin';
61
+ const NO_INFO_TEXT = "Không có thông tin";
62
+ const UPLOAD_FILE_TEXT = "Tải lên tập tin";
63
+ const NO_FILE_NAME = "Chưa có thông tin tên tập tin";
64
+ const UPLOAD_FILE_ERROR = "Lỗi trong quá trình tải tập tin đến máy chủ!";
65
+ const DELETE_FILE_ERROR = "Lỗi trong quá trình xóa tập tin đến máy chủ!";
54
66
 
55
67
  const IMAGE_TYPES = {
56
68
  apng: 'image/apng',
@@ -403,6 +415,55 @@ var CustomValidaterEnum;
403
415
  CustomValidaterEnum["LeadingSpace"] = "leadingSpace";
404
416
  })(CustomValidaterEnum || (CustomValidaterEnum = {}));
405
417
 
418
+ var RegexTypeEnum;
419
+ (function (RegexTypeEnum) {
420
+ RegexTypeEnum["Alpha"] = "^[a-zA-Z]+$";
421
+ RegexTypeEnum["AlphaNumeric"] = "^[a-zA-Z0-9]+$";
422
+ RegexTypeEnum["Name"] = "^[0-9a-zA-Z_ \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u00FD\u1EF7\u1EF9\\.+,:%&=;s.\\/\\-_'\\()]+$";
423
+ RegexTypeEnum["Username"] = "^[a-z0-9_.s]+$";
424
+ RegexTypeEnum["RegexPostCode"] = "^[a-zA-Z0-9-s]+$";
425
+ RegexTypeEnum["AlphaDash"] = "^[a-zA-Z0-9_-s.]+$";
426
+ RegexTypeEnum["AlphaVN"] = "^[a-zA-Z_ \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u00FD\u1EF7\u1EF9\\,s]+$";
427
+ RegexTypeEnum["AlphaNumericVN"] = "^[0-9a-zA-Z_ \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u00FD\u1EF7\u1EF9\\s]+$";
428
+ RegexTypeEnum["AlphaDashVN"] = "^[0-9a-zA-Z_ \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u00FD\u1EF7\u1EF9\\.+,:%&=;s.\\/\\-_'\\()]+$";
429
+ RegexTypeEnum["UnitName"] = "^[a-zA-Z0-9 \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9s_-]+$";
430
+ RegexTypeEnum["Date"] = "([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))";
431
+ RegexTypeEnum["Time"] = "(?=[01]\\d|2[0123])=(?=[012345]\\d)=(?=[012345]\\d)";
432
+ RegexTypeEnum["DateTime"] = "[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9])=[0-5][0-9]=[0-5][0-9]";
433
+ // dateVN= "^(0?[1-9]|[12][0-9]|3[01])[\\/](0?[1-9]|1[012])[\\/]\\d{4}$",
434
+ // dateVN= /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-](19|20)\d\d/,
435
+ // dateVN= "^([0-2][0-9]|(3)[0-1])(\-)(((0)[0-9])|((1)[0-2]))(\-)\d{4}$",
436
+ RegexTypeEnum["DateVN"] = "^(?=0[1-9]|[12]d|3[01])([/.-])(?=0[1-9]|1[012])\\1(?=19|20)dd$";
437
+ RegexTypeEnum["DateTimeVN"] = "^(0?[1-9]|[12][0-9]|3[01])[\\/](0?[1-9]|1[012])[\\/]\\d{4} (\\d{2})=(\\d{2})=(\\d{2})$";
438
+ RegexTypeEnum["DateShortTimeVN"] = "^(0?[1-9]|[12][0-9]|3[01])[\\/](0?[1-9]|1[012])[\\/]\\d{4} (\\d{2})=(\\d{2})$";
439
+ RegexTypeEnum["Integer"] = "^\\-?[0-9]+$";
440
+ RegexTypeEnum["Decimal"] = "^\\-?[0-9]*\\.?[0-9]+$";
441
+ RegexTypeEnum["IpAddress"] = "^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$";
442
+ RegexTypeEnum["Base64"] = "[^a-zA-Z0-9\\/\\+=]";
443
+ RegexTypeEnum["Indentityid"] = "^\\d{12}$";
444
+ // dnkhcn = "/^DNKHCN-[A-Z]{2}-d{4}$/",
445
+ // msdn = "/^d{10}$/",
446
+ RegexTypeEnum["IntergerNotZero"] = "^[0-9]*[1-9][0-9]*$";
447
+ //link = "(https?=//(?=www.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9].[^s]{2,}|www.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9].[^s]{2,}|https?=//(?=www.|(?!www))[a-zA-Z0-9]+.[^s]{2,}|www.[a-zA-Z0-9]+.[^s]{2,})",
448
+ // code = "^['A-Z''0-9''._\\-']*$",
449
+ RegexTypeEnum["Code"] = "^['A-Z''0-9''./_\\-\\/\\\u0110']*$";
450
+ RegexTypeEnum["CodeHsda"] = "^['A-Z''0-9''./_\\-']*$";
451
+ RegexTypeEnum["LinkLocation"] = "(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})([/\\w .-]*)?(\\?[\\w=&,.%-]*)?";
452
+ RegexTypeEnum["Link"] = "(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})[/\\w .-]*/?";
453
+ //password = "/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[$@$!%*?&]).{8,}/",
454
+ RegexTypeEnum["AccountName"] = "^[a-zA-Z0-9_.]{2,20}$";
455
+ RegexTypeEnum["NameOrFullName"] = "^[a-zA-Z0-9_\u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EBF\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9 ]{1,20}$";
456
+ RegexTypeEnum["PackageName"] = "^(?:[a-zA-Z]+(?:d*[a-zA-Z_]*)*)(?:.[a-zA-Z]+(?:d*[a-zA-Z_]*)*)+$";
457
+ RegexTypeEnum["Email"] = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$";
458
+ RegexTypeEnum["PasswordRegex"] = "^(?!.*\\s)(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[~`!@#$%^&*()--+={}\\[\\]|\\\\:;\"'<>,.?/_\u20B9]).{8,}$";
459
+ // phoneNumber = "^(\\+84|0)([235789])([0-9]{8,9})\\b",
460
+ RegexTypeEnum["PhoneNumber"] = "^(\\+84|0)([235789])([0-9]{8,9})$";
461
+ RegexTypeEnum["SeqNum"] = "^(100|[0-9]{1,2})$";
462
+ RegexTypeEnum["LinkBasemap"] = "(https://www.|http://www.|https://|http://)?[a-zA-Z]{2,}(.[a-zA-Z]{2,})(.[a-zA-Z]{2,})?/[a-zA-Z0-9]{2,}|((https://www.|http://www.|https://|http://)?[a-zA-Z]{2,}(.[a-zA-Z]{2,})(.[a-zA-Z]{2,})?)|(https://www.|http://www.|https://|http://)?[a-zA-Z0-9]{2,}.[a-zA-Z0-9]{2,}.[a-zA-Z0-9]{2,}(.[a-zA-Z0-9]{2,})?";
463
+ RegexTypeEnum["AlphaDashBreakLineVN"] = "^[0-9a-zA-Z_ \u00C0\u00C1\u00C2\u00C3\u00C8\u00C9\u00CA\u00CC\u00CD\u00D2\u00D3\u00D4\u00D5\u00D9\u00DA\u0102\u0110\u0128\u0168\u01A0\u00E0\u00E1\u00E2\u00E3\u00E8\u00E9\u00EA\u00EC\u00ED\u00F2\u00F3\u00F4\u00F5\u00F9\u00FA\u0103\u0111\u0129\u0169\u01A1\u01AF\u0102\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EC0\u1EC0\u1EC2\u1EBE\u01B0\u0103\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EC1\u1EC1\u1EC3\u1EBF\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u00DD\u1EF6\u1EF8\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u00FD\u1EF7\u1EF9\\.+,:%&=;s.\\/\\-_'\\(\\)\n]+$";
464
+ RegexTypeEnum["Fax"] = "^(\\+?\\d{1,4}[\\s-]?)?(\\(?\\d{2,4}\\)?[\\s-]?)?[\\d\\s-]{5,15}$";
465
+ })(RegexTypeEnum || (RegexTypeEnum = {}));
466
+
406
467
  var FormModeEnum;
407
468
  (function (FormModeEnum) {
408
469
  FormModeEnum["Add"] = "add";
@@ -461,7 +522,7 @@ class BaseField {
461
522
  // Thứ tự hiển thị của field
462
523
  this.seqNum = 0;
463
524
  if (!init?.key || !init?.label) {
464
- throw new Error('key, label, and type are required');
525
+ throw new Error("key, label, and type are required");
465
526
  }
466
527
  /**
467
528
  * Gán giá trị cho các props cần giá trị mặc định ở BaseField.
@@ -471,11 +532,15 @@ class BaseField {
471
532
  this.grid = { xs: 24 };
472
533
  this.labelGrid = { xs: 24 };
473
534
  this.controlGrid = { xs: 24 };
474
- this.labelAlign = 'left';
535
+ this.labelAlign = "left";
475
536
  this.isHidden = false;
476
537
  this.isDisabled = false;
477
538
  this.isParent = false;
478
- this.allowSetValue = [FormModeEnum.Add, FormModeEnum.Edit, FormModeEnum.View];
539
+ this.allowSetValue = [
540
+ FormModeEnum.Add,
541
+ FormModeEnum.Edit,
542
+ FormModeEnum.View,
543
+ ];
479
544
  this.viewMode = ViewModeEnum.form;
480
545
  this.enableFieldChange = false;
481
546
  Object.assign(this, init);
@@ -496,14 +561,14 @@ class BaseField {
496
561
  }
497
562
  getErrorMessage(errorKey, errorValue, allFields) {
498
563
  if (errorKey === CommonValidaterEnum.Required) {
499
- return `${this.label || 'Trường này'} là bắt buộc`;
564
+ return `${this.label || "Trường này"} là bắt buộc`;
500
565
  }
501
566
  if (errorKey === CustomValidaterEnum.MatchField) {
502
567
  const matchKey = this.matchField;
503
- const targetLabel = allFields.find(f => f.key === matchKey)?.label || matchKey;
568
+ const targetLabel = allFields.find((f) => f.key === matchKey)?.label || matchKey;
504
569
  return `${this.label} không khớp với ${targetLabel}`;
505
570
  }
506
- return '';
571
+ return "";
507
572
  }
508
573
  }
509
574
 
@@ -761,6 +826,7 @@ var FieldTypeEnum;
761
826
  (function (FieldTypeEnum) {
762
827
  FieldTypeEnum["InputText"] = "input-text";
763
828
  FieldTypeEnum["InputPassword"] = "input-password";
829
+ FieldTypeEnum["InputFormatted"] = "input-formatted";
764
830
  FieldTypeEnum["Textarea"] = "textarea";
765
831
  FieldTypeEnum["DatePicker"] = "date-picker";
766
832
  FieldTypeEnum["DateRange"] = "date-range";
@@ -786,6 +852,14 @@ var InputFieldTypeEnum;
786
852
  InputFieldTypeEnum["Number"] = "number";
787
853
  })(InputFieldTypeEnum || (InputFieldTypeEnum = {}));
788
854
 
855
+ var NumericPreset;
856
+ (function (NumericPreset) {
857
+ NumericPreset["Integer"] = "integer";
858
+ NumericPreset["Decimal"] = "decimal";
859
+ NumericPreset["Currency"] = "currency";
860
+ NumericPreset["Manual"] = "manual";
861
+ })(NumericPreset || (NumericPreset = {}));
862
+
789
863
  var DateFieldTypeEnum;
790
864
  (function (DateFieldTypeEnum) {
791
865
  DateFieldTypeEnum["Picker"] = "picker";
@@ -855,6 +929,30 @@ class InputPasswordField extends InputBaseField {
855
929
  }
856
930
  }
857
931
 
932
+ /**
933
+ * Interface của Input Field dạng format.
934
+ */
935
+ class InputFormattedField extends InputBaseField {
936
+ constructor(init) {
937
+ super(init);
938
+ /**
939
+ * Gán giá trị cho các props cần giá trị mặc định ở InputFormatted.
940
+ */
941
+ this.type = FieldTypeEnum.InputFormatted;
942
+ this.format = NumericPreset.Integer;
943
+ Object.assign(this, init);
944
+ }
945
+ // Tạo Form Control cho Input Formatted Field.
946
+ toFormControl() {
947
+ const commonValidators = this.getInputCommonValidators();
948
+ const customValidators = this.getInputCustomValidators();
949
+ return new FormControl(this.value ?? "", [
950
+ ...commonValidators,
951
+ ...customValidators,
952
+ ]);
953
+ }
954
+ }
955
+
858
956
  const buildForm = (fields, mode, model) => {
859
957
  const groupObj = {};
860
958
  // console.log("model", model);
@@ -889,6 +987,34 @@ const buildForm = (fields, mode, model) => {
889
987
  }
890
988
  return new FormGroup(groupObj);
891
989
  };
990
+ // Tạo tự động placeholder cho các dạng control nhập với label hoặc key từ field
991
+ const createInputPlaceholder = (field) => {
992
+ const label = field?.label;
993
+ if (label)
994
+ return `Nhập ${label?.toLowerCase()}`;
995
+ return `Nhập ${field.key?.toLowerCase()}`;
996
+ };
997
+ // Tạo tự động placeholder cho các dạng single select với label hoặc key từ field
998
+ const createSingleSelectPlaceholder = (field) => {
999
+ const label = field?.label;
1000
+ if (label)
1001
+ return `Chọn ${label?.toLowerCase()}`;
1002
+ return `Chọn ${field.key?.toLowerCase()}`;
1003
+ };
1004
+ // Tạo tự động placeholder cho các dạng multi select với label hoặc key từ field
1005
+ const createMultiSelectPlaceholder = (field) => {
1006
+ const label = field?.label;
1007
+ if (label)
1008
+ return `Chọn các ${label?.toLowerCase()}`;
1009
+ return `Chọn các ${field.key?.toLowerCase()}`;
1010
+ };
1011
+ // Tạo tự động placeholder cho các dạng date-picker với label hoặc key từ field
1012
+ const createDatePickerPlaceholder = (field) => {
1013
+ const label = field?.label;
1014
+ if (label)
1015
+ return `Chọn ${label?.toLowerCase()}`;
1016
+ return `Chọn ${field.key?.toLowerCase()}`;
1017
+ };
892
1018
  // Tạo placeholder mặc định cho các dạng Input, Textarea.
893
1019
  const createPlaceholder = (field) => {
894
1020
  const placeholder = field?.placeholder;
@@ -907,7 +1033,7 @@ const createSelectPlaceholder = (field) => {
907
1033
  const label = field?.label;
908
1034
  if (label) {
909
1035
  const isSingleSelect = field?.type === FieldTypeEnum.SingleSelect;
910
- return `Chọn ${isSingleSelect ? '' : 'các'} ${label?.toLowerCase()}`;
1036
+ return `Chọn ${isSingleSelect ? "" : "các"} ${label?.toLowerCase()}`;
911
1037
  }
912
1038
  return `Nhập ${field.key?.toLowerCase()}`;
913
1039
  };
@@ -1067,7 +1193,10 @@ class SelectBaseField extends BaseField {
1067
1193
  toFormControl() {
1068
1194
  const commonValidators = this.getCommonSelectValidators();
1069
1195
  const customValidators = this.getCustomSelectValidators();
1070
- return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
1196
+ return new FormControl(this.value ?? "", [
1197
+ ...commonValidators,
1198
+ ...customValidators,
1199
+ ]);
1071
1200
  }
1072
1201
  getErrorMessage(errorKey, errorValue, allFields) {
1073
1202
  return super.getErrorMessage(errorKey, errorValue, allFields);
@@ -1174,23 +1303,24 @@ class BaseDateField extends BaseField {
1174
1303
  * FIELD_TYPES là readonly ['input', 'select', 'checkbox',...]
1175
1304
  */
1176
1305
  const FIELD_TYPES = [
1177
- 'input-text',
1178
- 'input-password',
1179
- 'textarea',
1180
- 'date-picker',
1181
- 'date-range',
1182
- 'single-select',
1183
- 'tree-select',
1184
- 'multi-select',
1185
- 'single-file-upload',
1186
- 'multi-file-upload',
1187
- 'multi-image-upload',
1188
- 'avatar-upload',
1189
- 'checkbox',
1190
- 'radio',
1191
- 'reminder-type-radio',
1192
- 'switch',
1193
- 'autocomplete',
1306
+ "input-text",
1307
+ "input-password",
1308
+ "input-formatted",
1309
+ "textarea",
1310
+ "date-picker",
1311
+ "date-range",
1312
+ "single-select",
1313
+ "tree-select",
1314
+ "multi-select",
1315
+ "single-file-upload",
1316
+ "multi-file-upload",
1317
+ "multi-image-upload",
1318
+ "avatar-upload",
1319
+ "checkbox",
1320
+ "radio",
1321
+ "reminder-type-radio",
1322
+ "switch",
1323
+ "autocomplete",
1194
1324
  ];
1195
1325
 
1196
1326
  /**
@@ -1208,6 +1338,33 @@ const FORMAT_DATE_PATTERNS = {
1208
1338
  MM_DD_YYYY_HMS: /^(0[1-9]|1[0-2])([-\/])(0[1-9]|[12]\d|3[01])\2(\d{4})\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/,
1209
1339
  };
1210
1340
 
1341
+ const BASE_OPTIONS = {
1342
+ modifyValueOnWheel: false,
1343
+ selectOnFocus: false,
1344
+ };
1345
+ const NUMERIC_PRESETS = {
1346
+ [NumericPreset.Integer]: {
1347
+ digitGroupSeparator: ".",
1348
+ decimalCharacter: ",",
1349
+ decimalPlaces: 0,
1350
+ minimumValue: "0",
1351
+ },
1352
+ [NumericPreset.Decimal]: {
1353
+ digitGroupSeparator: ".",
1354
+ decimalCharacter: ",",
1355
+ decimalPlaces: 2,
1356
+ minimumValue: "0",
1357
+ },
1358
+ [NumericPreset.Currency]: {
1359
+ digitGroupSeparator: ".",
1360
+ decimalCharacter: ",",
1361
+ decimalPlaces: 0,
1362
+ currencySymbol: " ₫",
1363
+ currencySymbolPlacement: "s",
1364
+ minimumValue: "0",
1365
+ },
1366
+ };
1367
+
1211
1368
  // Chuyển đổi đối tượng Date thành string có format DDMMYYYY.
1212
1369
  const formatDateToDDMMYYYY = (date, separator = '-') => {
1213
1370
  const dd = String(date.getDate()).padStart(2, '0');
@@ -1379,8 +1536,8 @@ class DateRangeField extends BaseDateField {
1379
1536
  }
1380
1537
  toFormValue(modelValue) {
1381
1538
  let value = modelValue;
1382
- if (typeof value === 'string') {
1383
- value = value.split(',').map(dateStr => dateStr.trim());
1539
+ if (typeof value === "string") {
1540
+ value = value.split(",").map((dateStr) => dateStr.trim());
1384
1541
  }
1385
1542
  let dateRange = [];
1386
1543
  if (value?.length === 2) {
@@ -1495,7 +1652,7 @@ class MultiFileUploadField extends BaseUploadField {
1495
1652
  }
1496
1653
  }
1497
1654
 
1498
- const DEFAULT_MULTI_IMAGE_UPLOAD_VALUE = '';
1655
+ const DEFAULT_MULTI_IMAGE_UPLOAD_VALUE = "";
1499
1656
  /**
1500
1657
  * Interface của Multi image upload.
1501
1658
  */
@@ -1509,8 +1666,8 @@ class MultiImageUploadField extends BaseUploadField {
1509
1666
  this.value = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1510
1667
  this.initialValue = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1511
1668
  this.maxFileCount = 6;
1512
- this.imageCardWidth = '108px';
1513
- this.imageCardHeight = '108px';
1669
+ this.imageCardWidth = "108px";
1670
+ this.imageCardHeight = "108px";
1514
1671
  Object.assign(this, init);
1515
1672
  }
1516
1673
  getDefaultValue() {
@@ -1863,7 +2020,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
1863
2020
  class FieldService {
1864
2021
  constructor() {
1865
2022
  this.httpClient = inject(HttpClient);
2023
+ // =========================================================================
2024
+ // --- CODE CŨ (Sử dụng Signal cho luồng phụ thuộc) ---
2025
+ // =========================================================================
1866
2026
  this.dependValuesMap = new Map();
2027
+ // =========================================================================
2028
+ // --- CODE MỚI (Sử dụng RxJS BehaviorSubject cho luồng phụ thuộc) ---
2029
+ // =========================================================================
2030
+ // Giải thích kiến trúc mới:
2031
+ // - Dùng BehaviorSubject vì nó lưu lại giá trị cuối cùng. Khi một field Con
2032
+ // được khởi tạo sau field Cha và subscribe vào, nó sẽ lập tức nhận được
2033
+ // giá trị hiện hành thay vì phải chờ Cha thay đổi lần nữa.
2034
+ // - Cách này giúp thiết lập luồng dữ liệu dạng Push, rất dễ dàng kết hợp
2035
+ // với rxResource hoặc switchMap ở phía Component Con để triệt tiêu lỗi Race-condition.
2036
+ this.dependValuesSubjectMap = new Map();
1867
2037
  // Signal lưu trữ đối tượng chứa các prop là fieldKey có isParent là true.
1868
2038
  this.dependValues = signal({});
1869
2039
  // Signal lưu giữ xem field có isParent gần nhất vừa change giá trị.
@@ -1889,9 +2059,33 @@ class FieldService {
1889
2059
  getDependValuesMap() {
1890
2060
  return this.dependValuesMap;
1891
2061
  }
2062
+ /**
2063
+ * Bắn (Emit) giá trị thay đổi của một trường Parent vào stream.
2064
+ * Sẽ được gọi bởi Component đóng vai trò là Cha (isParent = true).
2065
+ */
2066
+ emitDependValueChange(key, value) {
2067
+ if (!this.dependValuesSubjectMap.has(key)) {
2068
+ this.dependValuesSubjectMap.set(key, new BehaviorSubject(value));
2069
+ return;
2070
+ }
2071
+ // Nếu đã có stream, đẩy giá trị mới vào ống nước.
2072
+ this.dependValuesSubjectMap.get(key).next(value);
2073
+ }
2074
+ /**
2075
+ * Trả về Observable của một trường để các Component Con có thể subscribe/nghe ngóng.
2076
+ * Sẽ được gọi bởi các Component có chứa `dependsOn`.
2077
+ */
2078
+ getDependValueChanges$(key) {
2079
+ if (!this.dependValuesSubjectMap.has(key)) {
2080
+ // Đề phòng trường hợp Con subscribe trước khi Cha khởi tạo (hoặc Cha chưa patch value)
2081
+ // Ta tạo sẵn một ống nước rỗng với giá trị ban đầu là null.
2082
+ this.dependValuesSubjectMap.set(key, new BehaviorSubject(null));
2083
+ }
2084
+ return this.dependValuesSubjectMap.get(key).asObservable();
2085
+ }
1892
2086
  // Cập nhật giá trị theo key vào đối tượng của signal.
1893
2087
  updateDependValues(key, value) {
1894
- this.dependValues.update(curr => ({ ...curr, [key]: value }));
2088
+ this.dependValues.update((curr) => ({ ...curr, [key]: value }));
1895
2089
  }
1896
2090
  // Xóa giá trị và loại bỏ key theo key khỏi đối tượng của signal.
1897
2091
  removeDependValues(key) {
@@ -1925,12 +2119,12 @@ class FieldService {
1925
2119
  });
1926
2120
  }
1927
2121
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1928
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldService, providedIn: 'root' }); }
2122
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldService, providedIn: "root" }); }
1929
2123
  }
1930
2124
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldService, decorators: [{
1931
2125
  type: Injectable,
1932
2126
  args: [{
1933
- providedIn: 'root',
2127
+ providedIn: "root",
1934
2128
  }]
1935
2129
  }] });
1936
2130
 
@@ -2048,22 +2242,44 @@ class BaseValueAccessor {
2048
2242
  this.control = null;
2049
2243
  // Chế độ form: Add, Edit, View...
2050
2244
  this.modeSignal = input(FormModeEnum.Add);
2051
- this.isAdd = computed(() => this.modeSignal() === 'add');
2052
- this.isEdit = computed(() => this.modeSignal() === 'edit');
2053
- this.isView = computed(() => this.modeSignal() === 'view');
2054
- this.isAddOrEdit = computed(() => ['add', 'edit'].includes(this.modeSignal()));
2055
- this.isEditOrView = computed(() => ['edit', 'view'].includes(this.modeSignal()));
2245
+ this.isAdd = computed(() => this.modeSignal() === "add");
2246
+ this.isEdit = computed(() => this.modeSignal() === "edit");
2247
+ this.isView = computed(() => this.modeSignal() === "view");
2248
+ this.isAddOrEdit = computed(() => ["add", "edit"].includes(this.modeSignal()));
2249
+ this.isEditOrView = computed(() => ["edit", "view"].includes(this.modeSignal()));
2056
2250
  this.formModeEnum = FormModeEnum;
2057
2251
  this.viewModeEnum = ViewModeEnum;
2058
2252
  this.destroy$ = new Subject();
2059
2253
  // Dành cho ControlValueAccessor.
2060
2254
  this.value = null;
2255
+ this.valueSignal = signal(null);
2061
2256
  this.onChange = () => { };
2062
2257
  this.onTouched = () => { };
2063
2258
  this.isDisabled = false;
2259
+ this.disabledSignal = signal(false); // Đang có select dùng isDisableSignal nên sau này đồng bộ lại.
2064
2260
  this.dFormService = inject(DynamicFormService);
2065
2261
  this.formDataService = inject(FormDataService);
2066
2262
  this.fieldService = inject(FieldService);
2263
+ this.resetEffect = effect(() => {
2264
+ const reset = this.dFormService.resetTrigger();
2265
+ if (reset?.formId === this.formId) {
2266
+ this.reset();
2267
+ }
2268
+ });
2269
+ // Dành cho UI
2270
+ this.viewMode = computed(() => this.fieldInput().viewMode);
2271
+ this.displayPlaceholder = computed(() => {
2272
+ if (!this.isAddOrEdit())
2273
+ return "";
2274
+ const placeholder = this.fieldInput()?.placeholder;
2275
+ if (placeholder)
2276
+ return placeholder;
2277
+ return this.resolvePlaceholderText();
2278
+ });
2279
+ }
2280
+ // 2. Hàm sinh text mặc định (Component con có thể override hàm này)
2281
+ resolvePlaceholderText() {
2282
+ return "Nhập dữ liệu";
2067
2283
  }
2068
2284
  ngOnDestroy() {
2069
2285
  this.destroy$.next();
@@ -2074,6 +2290,7 @@ class BaseValueAccessor {
2074
2290
  }
2075
2291
  writeValue(value) {
2076
2292
  this.value = value;
2293
+ this.valueSignal.set(value);
2077
2294
  this.writeValueToView(value); // 👈 component con override nếu cần.
2078
2295
  }
2079
2296
  registerOnChange(fn) {
@@ -2084,10 +2301,13 @@ class BaseValueAccessor {
2084
2301
  }
2085
2302
  setDisabledState(isDisabled) {
2086
2303
  this.isDisabled = isDisabled;
2304
+ this.disabledSignal.set(isDisabled);
2087
2305
  this.setDisabledOnView(isDisabled);
2088
2306
  }
2089
2307
  propagateValue(value) {
2090
2308
  this.value = value;
2309
+ this.valueSignal.set(value);
2310
+ this.fieldService.onFieldChange(this.fieldKey(), value);
2091
2311
  this.onChange(this.value);
2092
2312
  // Khi thay đổi value.
2093
2313
  this.handleValueChange(value);
@@ -2128,6 +2348,9 @@ class BaseValueAccessor {
2128
2348
  enableControl() {
2129
2349
  this.control?.enable();
2130
2350
  }
2351
+ resetControl() {
2352
+ this.control?.reset();
2353
+ }
2131
2354
  /**
2132
2355
  * Optional: để component override riêng.
2133
2356
  */
@@ -2156,22 +2379,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
2156
2379
  args: [{ required: false }]
2157
2380
  }] } });
2158
2381
 
2159
- const ZORRO_MODULES$e = [NzDatePickerModule, NzInputModule];
2382
+ const ZORRO_MODULES$a = [NzDatePickerModule, NzInputModule];
2160
2383
  class DatePickerComponent extends BaseValueAccessor {
2161
2384
  constructor() {
2162
2385
  super();
2163
- this.fieldSignal = computed(() => {
2386
+ this.field = computed(() => {
2164
2387
  const base = this.fieldInput();
2165
2388
  if (!(base instanceof DatePickerField) ||
2166
- (base.type !== FieldTypeEnum.DatePicker && base.controlType !== ControlTypeEnum.Date)) {
2167
- throw new Error('Expected DatePickerField');
2389
+ (base.type !== FieldTypeEnum.DatePicker &&
2390
+ base.controlType !== ControlTypeEnum.Date)) {
2391
+ throw new Error("Expected DatePickerField");
2168
2392
  }
2169
2393
  return base;
2170
2394
  });
2171
- this.displayValue = '';
2395
+ // Định dạng ngày
2396
+ this.format = computed(() => {
2397
+ return this.field().format ?? "dd-MM-yyyy";
2398
+ });
2172
2399
  // Kiểm tra có cho phép chọn ngày quá khứ hay ngày tương lai hay không?
2173
2400
  this.disabledDate = computed(() => {
2174
- const field = this.fieldSignal();
2401
+ const field = this.field();
2175
2402
  return (current) => {
2176
2403
  if (field.disabledDateFn) {
2177
2404
  return field.disabledDateFn(current);
@@ -2186,52 +2413,44 @@ class DatePickerComponent extends BaseValueAccessor {
2186
2413
  return false;
2187
2414
  };
2188
2415
  });
2189
- // 1. Effect chung Add + Edit
2190
- effect(() => {
2191
- if (this.isAddOrEdit()) {
2192
- const field = untracked(() => this.fieldSignal());
2193
- const { placeholder } = field;
2194
- if (!placeholder)
2195
- field.placeholder = createPlaceholder(field);
2196
- }
2197
- });
2198
- // Trigger reset từ bên ngoài form.
2199
- effect(() => {
2200
- const reset = this.dFormService.resetTrigger();
2201
- if (reset?.formId === this.formId) {
2202
- this.reset();
2203
- }
2416
+ this.viewDisplayValue = computed(() => {
2417
+ // 1. Nếu không phải View thì không cần quan tâm text này
2418
+ if (!this.isView())
2419
+ return "";
2420
+ // 2. Lấy giá trị từ valueSignal
2421
+ const val = this.valueSignal();
2422
+ return val ? FormUtils.convertViewDate(val, this.format()) : NO_INFO_TEXT;
2204
2423
  });
2205
2424
  }
2425
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
2426
+ resolvePlaceholderText() {
2427
+ return createDatePickerPlaceholder(this.field());
2428
+ }
2429
+ // Sự kiện khi người dùng chọn ngày
2206
2430
  handleChangeDate(date) {
2207
2431
  this.propagateValue(date);
2208
2432
  }
2209
- // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
2433
+ // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue
2210
2434
  writeValueToView(value) {
2211
- if (this.isView()) {
2212
- this.displayValue = value
2213
- ? FormUtils.convertViewDate(value, this.fieldSignal().format ?? 'dd-MM-yyyy')
2214
- : NO_INFO_TEXT;
2215
- return;
2216
- }
2217
2435
  if (!this.isAddOrEdit())
2218
2436
  return;
2219
- const { outputAs } = untracked(() => this.fieldSignal());
2220
- this.fieldService.updateDependValuesMap(this.fieldKey(), value ? formatDateToYYYYMMDD(value) : '');
2221
- // Gán data vào đầu ra của form.
2437
+ const { outputAs } = this.field();
2438
+ this.fieldService.updateDependValuesMap(this.fieldKey(), value ? formatDateToYYYYMMDD(value) : "");
2439
+ // Gán data vào đầu ra của form
2222
2440
  const newOutputValue = value && this.getValueByOutputAs(value, outputAs);
2223
2441
  this.patchFormData(this.fieldKey(), newOutputValue);
2224
2442
  }
2225
2443
  // Xử lý phụ thuộc khi change value
2226
2444
  handleValueChange(value) {
2227
- const { outputAs, isParent } = untracked(() => this.fieldSignal());
2445
+ const { outputAs, isParent } = this.field();
2228
2446
  if (isParent) {
2229
- this.fieldService.updateDependValuesMap(this.fieldKey(), value ? formatDateToYYYYMMDD(value) : '');
2447
+ this.fieldService.updateDependValuesMap(this.fieldKey(), value ? formatDateToYYYYMMDD(value) : "");
2230
2448
  }
2231
- // Gán data vào đầu ra của form.
2449
+ // Gán data vào đầu ra của form
2232
2450
  const newOutputValue = value && this.getValueByOutputAs(value, outputAs);
2233
2451
  this.patchFormData(this.fieldKey(), newOutputValue);
2234
2452
  }
2453
+ // Biến đổi giá trị đầu ra theo outputAs
2235
2454
  getValueByOutputAs(date, outputAs) {
2236
2455
  let value = date;
2237
2456
  if (outputAs === DatePickerOutputValueTypeEnum.String) {
@@ -2246,35 +2465,44 @@ class DatePickerComponent extends BaseValueAccessor {
2246
2465
  useExisting: forwardRef(() => DatePickerComponent),
2247
2466
  multi: true,
2248
2467
  },
2249
- ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n <input nz-input [value]=\"displayValue\" [disabled]=\"true\" class=\"modeView\" />\r\n} @else {\r\n <nz-date-picker\r\n [id]=\"fieldSignal().key\"\r\n [class]=\"'w-100'\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDate($event)\"\r\n >\r\n </nz-date-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .ant-input[disabled]{background-color:#fff}.modeView{pointer-events:none;color:#575757!important;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i2.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzAllowClear", "nzAutoFocus", "nzDisabled", "nzBorderless", "nzInputReadOnly", "nzInline", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }] }); }
2468
+ ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n <input\r\n nz-input\r\n [value]=\"viewDisplayValue()\"\r\n [disabled]=\"true\"\r\n class=\"modeView\"\r\n />\r\n} @else {\r\n <nz-date-picker\r\n [id]=\"fieldKey()\"\r\n [class]=\"'w-100'\"\r\n [nzDisabled]=\"disabledSignal()\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [ngModel]=\"valueSignal()\"\r\n (ngModelChange)=\"handleChangeDate($event)\"\r\n >\r\n </nz-date-picker>\r\n}\r\n", styles: [":host ::ng-deep .ant-input[disabled]{background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i2.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzAllowClear", "nzAutoFocus", "nzDisabled", "nzBorderless", "nzInputReadOnly", "nzInline", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }] }); }
2250
2469
  }
2251
2470
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DatePickerComponent, decorators: [{
2252
2471
  type: Component,
2253
- args: [{ selector: 'app-date-picker', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$e], providers: [
2472
+ args: [{ selector: "app-date-picker", standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$a], providers: [
2254
2473
  {
2255
2474
  provide: NG_VALUE_ACCESSOR,
2256
2475
  useExisting: forwardRef(() => DatePickerComponent),
2257
2476
  multi: true,
2258
2477
  },
2259
- ], template: "@if (modeSignal() === formModeEnum.View) {\r\n <input nz-input [value]=\"displayValue\" [disabled]=\"true\" class=\"modeView\" />\r\n} @else {\r\n <nz-date-picker\r\n [id]=\"fieldSignal().key\"\r\n [class]=\"'w-100'\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDate($event)\"\r\n >\r\n </nz-date-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .ant-input[disabled]{background-color:#fff}.modeView{pointer-events:none;color:#575757!important;font-size:12px}\n"] }]
2478
+ ], template: "@if (modeSignal() === formModeEnum.View) {\r\n <input\r\n nz-input\r\n [value]=\"viewDisplayValue()\"\r\n [disabled]=\"true\"\r\n class=\"modeView\"\r\n />\r\n} @else {\r\n <nz-date-picker\r\n [id]=\"fieldKey()\"\r\n [class]=\"'w-100'\"\r\n [nzDisabled]=\"disabledSignal()\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [ngModel]=\"valueSignal()\"\r\n (ngModelChange)=\"handleChangeDate($event)\"\r\n >\r\n </nz-date-picker>\r\n}\r\n", styles: [":host ::ng-deep .ant-input[disabled]{background-color:#fff}\n"] }]
2260
2479
  }], ctorParameters: () => [] });
2261
2480
 
2262
- const ZORRO_MODULES$d = [NzDatePickerModule, NzInputModule];
2481
+ const ZORRO_MODULES$9 = [NzDatePickerModule, NzInputModule];
2263
2482
  class DateRangeComponent extends BaseValueAccessor {
2264
2483
  constructor() {
2265
2484
  super();
2266
- this.fieldSignal = computed(() => {
2485
+ this.field = computed(() => {
2267
2486
  const base = this.fieldInput();
2268
2487
  if (!(base instanceof DateRangeField) ||
2269
- (base.type !== FieldTypeEnum.DateRange && base.controlType !== ControlTypeEnum.Date)) {
2270
- throw new Error('Expected DatePickerField');
2488
+ (base.type !== FieldTypeEnum.DateRange &&
2489
+ base.controlType !== ControlTypeEnum.Date)) {
2490
+ throw new Error("Expected DatePickerField");
2271
2491
  }
2272
2492
  return base;
2273
2493
  });
2274
- this.displayValueNoInfo = '';
2494
+ // Định dạng ngày
2495
+ this.format = computed(() => {
2496
+ return this.field().format ?? "dd-MM-yyyy";
2497
+ });
2498
+ this.placeholders = computed(() => {
2499
+ return this.field().placeholders ?? ["Ngày bắt đầu", "Ngày kết thúc"];
2500
+ });
2501
+ // Nếu mảng date không lớn hơn 2
2502
+ this.displayValueNoInfo = NO_INFO_TEXT;
2275
2503
  // Kiểm tra có cho phép chọn ngày quá khứ hay ngày tương lai hay không?
2276
2504
  this.disabledDate = computed(() => {
2277
- const field = this.fieldSignal();
2505
+ const field = this.field();
2278
2506
  return (current) => {
2279
2507
  const today = new Date();
2280
2508
  const diff = differenceInCalendarDays(current, today);
@@ -2286,44 +2514,21 @@ class DateRangeComponent extends BaseValueAccessor {
2286
2514
  return false;
2287
2515
  };
2288
2516
  });
2289
- // Trigger reset từ bên ngoài form.
2290
- effect(() => {
2291
- const reset = this.dFormService.resetTrigger();
2292
- if (reset?.formId === this.formId) {
2293
- this.reset();
2294
- }
2295
- });
2296
- }
2297
- getSelectedDateRangeFromFieldValue(fieldValue) {
2298
- let dateRange = [];
2299
- if (fieldValue?.length === 2) {
2300
- const startDate = normalizeToDate(fieldValue[0]);
2301
- const endDate = normalizeToDate(fieldValue[1]);
2302
- if (startDate && endDate)
2303
- dateRange = [startDate, endDate];
2304
- }
2305
- return dateRange;
2306
2517
  }
2307
2518
  handleChangeDateRange(dateRange) {
2308
2519
  this.propagateValue(dateRange);
2309
2520
  }
2310
2521
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
2311
2522
  writeValueToView(value) {
2312
- if (this.isView()) {
2313
- if (value.length === 0) {
2314
- this.displayValueNoInfo = NO_INFO_TEXT;
2315
- return;
2316
- }
2317
- }
2318
2523
  if (!this.isAddOrEdit())
2319
2524
  return;
2320
- const { outputAs } = untracked(() => this.fieldSignal());
2525
+ const { outputAs } = untracked(() => this.field());
2321
2526
  // Gán data vào đầu ra của form.
2322
2527
  this.patchFormData(this.fieldKey(), value.length > 0 ? this.getValueByOutputAs(value, outputAs) : null);
2323
2528
  }
2324
2529
  // Xử lý phụ thuộc khi change value
2325
2530
  handleValueChange(value) {
2326
- const { outputAs } = untracked(() => this.fieldSignal());
2531
+ const { outputAs } = untracked(() => this.field());
2327
2532
  // Gán data vào đầu ra của form.
2328
2533
  this.patchFormData(this.fieldKey(), value.length > 0 ? this.getValueByOutputAs(value, outputAs) : null);
2329
2534
  }
@@ -2335,7 +2540,10 @@ class DateRangeComponent extends BaseValueAccessor {
2335
2540
  value = `${formatDateToYYYYMMDD(dateRange[0])},${formatDateToYYYYMMDD(dateRange[1])}`;
2336
2541
  }
2337
2542
  if (outputAs === DateRangeOutputValueTypeEnum.StringArray) {
2338
- value = [formatDateToYYYYMMDD(dateRange[0]), formatDateToYYYYMMDD(dateRange[1])];
2543
+ value = [
2544
+ formatDateToYYYYMMDD(dateRange[0]),
2545
+ formatDateToYYYYMMDD(dateRange[1]),
2546
+ ];
2339
2547
  }
2340
2548
  }
2341
2549
  return value;
@@ -2347,72 +2555,17 @@ class DateRangeComponent extends BaseValueAccessor {
2347
2555
  useExisting: forwardRef(() => DateRangeComponent),
2348
2556
  multi: true,
2349
2557
  },
2350
- ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (value!.length > 0) {\r\n <nz-range-picker\r\n [class]=\"'w-100'\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabled]=\"true\"\r\n [(ngModel)]=\"value\"\r\n class=\"modeView\"\r\n [nzSuffixIcon]=\"suffixIcon\"\r\n ></nz-range-picker>\r\n <ng-template #suffixIcon>\r\n <!-- \u0110\u1EC3 tr\u1ED1ng, ho\u1EB7c custom icon kh\u00E1c -->\r\n </ng-template>\r\n } @else {\r\n <input nz-input [value]=\"displayValueNoInfo\" [disabled]=\"true\" class=\"modeView\" />\r\n }\r\n} @else {\r\n <nz-range-picker\r\n [id]=\"fieldSignal().key\"\r\n class=\"w-100\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder ?? ['Ng\u00E0y b\u1EAFt \u0111\u1EA7u', 'Ng\u00E0y k\u1EBFt th\u00FAc']\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDateRange($event)\"\r\n ></nz-range-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .modeView.ant-picker.ant-picker-disabled{background-color:inherit!important;cursor:default;pointer-events:none}:host ::ng-deep .modeView .ant-picker-input>input[disabled]{color:#575757!important}.modeView{pointer-events:none;background-color:#fff;font-size:12px;color:#575757!important}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i2.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzAllowClear", "nzAutoFocus", "nzDisabled", "nzBorderless", "nzInputReadOnly", "nzInline", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i2.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }] }); }
2558
+ ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (valueSignal()!.length > 0) {\r\n <nz-range-picker\r\n [class]=\"'w-100'\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabled]=\"true\"\r\n [ngModel]=\"valueSignal()\"\r\n class=\"modeView\"\r\n [nzSuffixIcon]=\"suffixIcon\"\r\n ></nz-range-picker>\r\n <ng-template #suffixIcon>\r\n <!-- \u0110\u1EC3 tr\u1ED1ng, ho\u1EB7c custom icon kh\u00E1c -->\r\n </ng-template>\r\n } @else {\r\n <input\r\n nz-input\r\n [value]=\"displayValueNoInfo\"\r\n [disabled]=\"true\"\r\n class=\"modeView\"\r\n />\r\n }\r\n} @else {\r\n <nz-range-picker\r\n [id]=\"fieldKey()\"\r\n class=\"w-100\"\r\n [nzDisabled]=\"disabledSignal()\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [nzPlaceHolder]=\"placeholders()\"\r\n [ngModel]=\"valueSignal()\"\r\n (ngModelChange)=\"handleChangeDateRange($event)\"\r\n ></nz-range-picker>\r\n}\r\n", styles: [":host ::ng-deep .modeView.ant-picker.ant-picker-disabled{background-color:inherit!important;cursor:default;pointer-events:none}:host ::ng-deep .modeView .ant-picker-input>input[disabled]{color:#575757!important}.modeView{pointer-events:none;color:#575757!important}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i2.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzAllowClear", "nzAutoFocus", "nzDisabled", "nzBorderless", "nzInputReadOnly", "nzInline", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i2.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }] }); }
2351
2559
  }
2352
2560
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DateRangeComponent, decorators: [{
2353
2561
  type: Component,
2354
- args: [{ selector: 'app-date-range', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$d], providers: [
2562
+ args: [{ selector: "app-date-range", standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$9], providers: [
2355
2563
  {
2356
2564
  provide: NG_VALUE_ACCESSOR,
2357
2565
  useExisting: forwardRef(() => DateRangeComponent),
2358
2566
  multi: true,
2359
2567
  },
2360
- ], template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (value!.length > 0) {\r\n <nz-range-picker\r\n [class]=\"'w-100'\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabled]=\"true\"\r\n [(ngModel)]=\"value\"\r\n class=\"modeView\"\r\n [nzSuffixIcon]=\"suffixIcon\"\r\n ></nz-range-picker>\r\n <ng-template #suffixIcon>\r\n <!-- \u0110\u1EC3 tr\u1ED1ng, ho\u1EB7c custom icon kh\u00E1c -->\r\n </ng-template>\r\n } @else {\r\n <input nz-input [value]=\"displayValueNoInfo\" [disabled]=\"true\" class=\"modeView\" />\r\n }\r\n} @else {\r\n <nz-range-picker\r\n [id]=\"fieldSignal().key\"\r\n class=\"w-100\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder ?? ['Ng\u00E0y b\u1EAFt \u0111\u1EA7u', 'Ng\u00E0y k\u1EBFt th\u00FAc']\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDateRange($event)\"\r\n ></nz-range-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .modeView.ant-picker.ant-picker-disabled{background-color:inherit!important;cursor:default;pointer-events:none}:host ::ng-deep .modeView .ant-picker-input>input[disabled]{color:#575757!important}.modeView{pointer-events:none;background-color:#fff;font-size:12px;color:#575757!important}\n"] }]
2361
- }], ctorParameters: () => [] });
2362
-
2363
- const ZORRO_MODULES$c = [NzInputModule, NzIconModule];
2364
- class InputPasswordComponent extends BaseValueAccessor {
2365
- constructor() {
2366
- super();
2367
- this.fieldSignal = computed(() => {
2368
- const base = this.fieldInput();
2369
- if (!(base instanceof InputPasswordField) ||
2370
- (base.type !== FieldTypeEnum.InputPassword && base.controlType !== ControlTypeEnum.Input)) {
2371
- throw new Error('Expected InputPasswordField');
2372
- }
2373
- return base;
2374
- });
2375
- this.passwordVisible = false;
2376
- // 1. Effect chung Add + Edit
2377
- effect(() => {
2378
- if (this.isAdd()) {
2379
- const field = this.fieldSignal();
2380
- const { placeholder } = field;
2381
- if (!placeholder)
2382
- field.placeholder = createPlaceholder(field);
2383
- }
2384
- });
2385
- // Trigger reset từ bên ngoài form.
2386
- effect(() => {
2387
- const reset = this.dFormService.resetTrigger();
2388
- if (reset?.formId === this.formId) {
2389
- this.reset();
2390
- }
2391
- });
2392
- }
2393
- handleChangeInputPassword(event) {
2394
- const inputValue = event.target.value;
2395
- this.propagateValue(inputValue);
2396
- this.patchFormData(this.fieldKey(), inputValue);
2397
- }
2398
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2399
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.15", type: InputPasswordComponent, isStandalone: true, selector: "app-input-password", providers: [
2400
- {
2401
- provide: NG_VALUE_ACCESSOR,
2402
- useExisting: forwardRef(() => InputPasswordComponent),
2403
- multi: true,
2404
- },
2405
- ], usesInheritance: true, ngImport: i0, template: "<nz-input-group [nzSuffix]=\"suffixTemplate\">\r\n <input\r\n nz-input\r\n [value]=\"value\"\r\n [type]=\"passwordVisible ? 'text' : 'password'\"\r\n autocomplete=\"new-password\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n (input)=\"handleChangeInputPassword($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n</nz-input-group>\r\n<ng-template #suffixTemplate>\r\n <span\r\n nz-icon\r\n [nzType]=\"passwordVisible ? 'eye-invisible' : 'eye'\"\r\n (click)=\"passwordVisible = !passwordVisible\"\r\n ></span>\r\n</ng-template>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: i1$1.NzInputGroupComponent, selector: "nz-input-group", inputs: ["nzAddOnBeforeIcon", "nzAddOnAfterIcon", "nzPrefixIcon", "nzSuffixIcon", "nzAddOnBefore", "nzAddOnAfter", "nzPrefix", "nzStatus", "nzSuffix", "nzSize", "nzSearch", "nzCompact"], exportAs: ["nzInputGroup"] }, { kind: "directive", type: i1$1.NzInputGroupWhitSuffixOrPrefixDirective, selector: "nz-input-group[nzSuffix], nz-input-group[nzPrefix]" }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }] }); }
2406
- }
2407
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputPasswordComponent, decorators: [{
2408
- type: Component,
2409
- args: [{ selector: 'app-input-password', standalone: true, imports: [...ZORRO_MODULES$c], providers: [
2410
- {
2411
- provide: NG_VALUE_ACCESSOR,
2412
- useExisting: forwardRef(() => InputPasswordComponent),
2413
- multi: true,
2414
- },
2415
- ], template: "<nz-input-group [nzSuffix]=\"suffixTemplate\">\r\n <input\r\n nz-input\r\n [value]=\"value\"\r\n [type]=\"passwordVisible ? 'text' : 'password'\"\r\n autocomplete=\"new-password\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n (input)=\"handleChangeInputPassword($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n</nz-input-group>\r\n<ng-template #suffixTemplate>\r\n <span\r\n nz-icon\r\n [nzType]=\"passwordVisible ? 'eye-invisible' : 'eye'\"\r\n (click)=\"passwordVisible = !passwordVisible\"\r\n ></span>\r\n</ng-template>\r\n" }]
2568
+ ], template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (valueSignal()!.length > 0) {\r\n <nz-range-picker\r\n [class]=\"'w-100'\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabled]=\"true\"\r\n [ngModel]=\"valueSignal()\"\r\n class=\"modeView\"\r\n [nzSuffixIcon]=\"suffixIcon\"\r\n ></nz-range-picker>\r\n <ng-template #suffixIcon>\r\n <!-- \u0110\u1EC3 tr\u1ED1ng, ho\u1EB7c custom icon kh\u00E1c -->\r\n </ng-template>\r\n } @else {\r\n <input\r\n nz-input\r\n [value]=\"displayValueNoInfo\"\r\n [disabled]=\"true\"\r\n class=\"modeView\"\r\n />\r\n }\r\n} @else {\r\n <nz-range-picker\r\n [id]=\"fieldKey()\"\r\n class=\"w-100\"\r\n [nzDisabled]=\"disabledSignal()\"\r\n [nzFormat]=\"format()\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [nzPlaceHolder]=\"placeholders()\"\r\n [ngModel]=\"valueSignal()\"\r\n (ngModelChange)=\"handleChangeDateRange($event)\"\r\n ></nz-range-picker>\r\n}\r\n", styles: [":host ::ng-deep .modeView.ant-picker.ant-picker-disabled{background-color:inherit!important;cursor:default;pointer-events:none}:host ::ng-deep .modeView .ant-picker-input>input[disabled]{color:#575757!important}.modeView{pointer-events:none;color:#575757!important}\n"] }]
2416
2569
  }], ctorParameters: () => [] });
2417
2570
 
2418
2571
  class SuffixService {
@@ -2433,7 +2586,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
2433
2586
  }]
2434
2587
  }], ctorParameters: () => [] });
2435
2588
 
2436
- const ZORRO_MODULES$b = [NzButtonModule, NzIconModule];
2589
+ const ZORRO_MODULES$8 = [NzButtonModule, NzIconModule];
2437
2590
  class SuffixComponent {
2438
2591
  constructor() {
2439
2592
  this.formId = '';
@@ -2447,73 +2600,215 @@ class SuffixComponent {
2447
2600
  this.suffixService.onSuffixClick(this.suffixConfig.key, this.formId);
2448
2601
  }
2449
2602
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SuffixComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2450
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: SuffixComponent, isStandalone: true, selector: "app-suffix", inputs: { suffixConfig: "suffixConfig", formId: "formId" }, ngImport: i0, template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.suffix-btn{margin-left:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }] }); }
2603
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: SuffixComponent, isStandalone: true, selector: "app-suffix", inputs: { suffixConfig: "suffixConfig", formId: "formId" }, ngImport: i0, template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".suffix-btn{margin-left:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }] }); }
2451
2604
  }
2452
2605
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SuffixComponent, decorators: [{
2453
2606
  type: Component,
2454
- args: [{ selector: 'app-suffix', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$b], template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.suffix-btn{margin-left:.5rem}\n"] }]
2607
+ args: [{ selector: 'app-suffix', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$8], template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".suffix-btn{margin-left:.5rem}\n"] }]
2455
2608
  }], propDecorators: { suffixConfig: [{
2456
2609
  type: Input
2457
2610
  }], formId: [{
2458
2611
  type: Input
2459
2612
  }] } });
2460
2613
 
2461
- const ZORRO_MODULES$a = [
2614
+ class AutoNumericOptionsFactory {
2615
+ static create(field) {
2616
+ if (field.format === NumericPreset.Manual) {
2617
+ return field.autoNumericOptions ?? NUMERIC_PRESETS.integer;
2618
+ }
2619
+ return NUMERIC_PRESETS[field.format];
2620
+ }
2621
+ }
2622
+
2623
+ const zorroModules$4 = [NzInputModule];
2624
+ class InputFormattedComponent extends BaseValueAccessor {
2625
+ constructor() {
2626
+ super();
2627
+ this.field = computed(() => {
2628
+ const base = this.fieldInput();
2629
+ if (!(base instanceof InputFormattedField) ||
2630
+ (base.type !== FieldTypeEnum.InputFormatted &&
2631
+ base.controlType !== ControlTypeEnum.Input)) {
2632
+ throw new Error("Expected InputFormattedField");
2633
+ }
2634
+ return base;
2635
+ });
2636
+ this.inputFormattedEle = viewChild("inputFormatted");
2637
+ this.autoNumericOptions = computed(() => AutoNumericOptionsFactory.create(this.field()));
2638
+ this.internalValue = signal("");
2639
+ this.inputDisplayValues = computed(() => {
2640
+ const isView = this.isView();
2641
+ const autoNumericOptions = this.autoNumericOptions();
2642
+ const value = this.internalValue();
2643
+ if (isView) {
2644
+ const viewValue = value
2645
+ ? AutoNumeric.format(value, autoNumericOptions)
2646
+ : undefined;
2647
+ return viewValue || NO_INFO_TEXT;
2648
+ }
2649
+ return "";
2650
+ });
2651
+ this.autoNumericLifeCycleEffect = effect((onCleanup) => {
2652
+ const nativeEle = this.inputFormattedEle()?.nativeElement;
2653
+ const isAddOrEdit = this.isAddOrEdit();
2654
+ const autoNumericOptions = untracked(() => this.autoNumericOptions());
2655
+ const value = untracked(() => this.valueSignal());
2656
+ // 1. TẠO BIẾN CỤC BỘ Ở ĐÂY (Đây là mấu chốt)
2657
+ let localAutoNumeric;
2658
+ if (nativeEle && isAddOrEdit) {
2659
+ // 2. Gán vào biến cục bộ
2660
+ localAutoNumeric = new AutoNumeric(nativeEle, value, autoNumericOptions);
2661
+ // 3. Vẫn cập nhật biến class để xài ở chỗ khác
2662
+ this.autoNumeric = localAutoNumeric;
2663
+ }
2664
+ onCleanup(() => {
2665
+ // 4. CHỈ REMOVE BIẾN CỤC BỘ (Không dùng this.autoNumeric ở đây)
2666
+ if (localAutoNumeric) {
2667
+ localAutoNumeric?.remove();
2668
+ // Dọn dẹp biến class nếu nó đang giữ cái vừa bị remove
2669
+ if (this.autoNumeric === localAutoNumeric) {
2670
+ this.autoNumeric = undefined;
2671
+ }
2672
+ }
2673
+ });
2674
+ });
2675
+ }
2676
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
2677
+ resolvePlaceholderText() {
2678
+ const isDisabled = this.field().isDisabled;
2679
+ return isDisabled ? NO_INFO_TEXT : createInputPlaceholder(this.field());
2680
+ }
2681
+ // event: Event;
2682
+ handleChangeInputText() {
2683
+ const { key } = this.field();
2684
+ // const inputValue = (event.target as HTMLInputElement).value;
2685
+ // Lấy giá trị RAW chuẩn từ AutoNumeric (không lấy event.target.value nữa)
2686
+ // Dùng fallback string rỗng nếu chưa có
2687
+ const rawNumericValue = this.autoNumeric?.getNumericString() || "";
2688
+ this.propagateValue(rawNumericValue);
2689
+ // Gán data vào đầu ra của form.
2690
+ this.patchFormData(key, rawNumericValue.trim());
2691
+ }
2692
+ // Chỗ này vẫn xử lý bình thường cho CVA không liên quan đến AutoNumeric.
2693
+ writeValueToView(value) {
2694
+ // 2. NẾU AutoNumeric ĐÃ ĐƯỢC TẠO (đang ở nhánh Add/Edit), thì ép nó format lại giá trị mới
2695
+ if (this.autoNumeric) {
2696
+ this.autoNumeric.set(value);
2697
+ }
2698
+ const { key } = untracked(() => this.field());
2699
+ // Gán data vào đầu ra của form.
2700
+ this.patchFormData(key, value);
2701
+ }
2702
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputFormattedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2703
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: InputFormattedComponent, isStandalone: true, selector: "app-input-formatted", providers: [
2704
+ {
2705
+ provide: NG_VALUE_ACCESSOR,
2706
+ useExisting: forwardRef(() => InputFormattedComponent),
2707
+ multi: true,
2708
+ },
2709
+ ], viewQueries: [{ propertyName: "inputFormattedEle", first: true, predicate: ["inputFormatted"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"inputDisplayValues()\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ inputDisplayValues() }}</span>\r\n }\r\n } @else {\r\n <!-- [value]=\"value\" -->\r\n\r\n <input\r\n #inputFormatted\r\n nz-input\r\n class=\"input-control custom-input\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText()\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".input-text-wrapper{display:flex}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2710
+ }
2711
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputFormattedComponent, decorators: [{
2712
+ type: Component,
2713
+ args: [{ selector: "app-input-formatted", standalone: true, imports: [...CORE_MODULES, ...FORM_MODULES, ...zorroModules$4, SuffixComponent], providers: [
2714
+ {
2715
+ provide: NG_VALUE_ACCESSOR,
2716
+ useExisting: forwardRef(() => InputFormattedComponent),
2717
+ multi: true,
2718
+ },
2719
+ ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"inputDisplayValues()\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ inputDisplayValues() }}</span>\r\n }\r\n } @else {\r\n <!-- [value]=\"value\" -->\r\n\r\n <input\r\n #inputFormatted\r\n nz-input\r\n class=\"input-control custom-input\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText()\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".input-text-wrapper{display:flex}\n"] }]
2720
+ }], ctorParameters: () => [] });
2721
+
2722
+ const ZORRO_MODULES$7 = [NzInputModule, NzIconModule];
2723
+ class InputPasswordComponent extends BaseValueAccessor {
2724
+ constructor() {
2725
+ super();
2726
+ this.field = computed(() => {
2727
+ const base = this.fieldInput();
2728
+ if (!(base instanceof InputPasswordField) ||
2729
+ (base.type !== FieldTypeEnum.InputPassword &&
2730
+ base.controlType !== ControlTypeEnum.Input)) {
2731
+ throw new Error("Expected InputPasswordField");
2732
+ }
2733
+ return base;
2734
+ });
2735
+ this.passwordVisible = false;
2736
+ }
2737
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
2738
+ resolvePlaceholderText() {
2739
+ return createInputPlaceholder(this.field());
2740
+ }
2741
+ handleChangeInputPassword(event) {
2742
+ const inputValue = event.target.value;
2743
+ this.propagateValue(inputValue);
2744
+ this.patchFormData(this.fieldKey(), inputValue);
2745
+ }
2746
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2747
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.15", type: InputPasswordComponent, isStandalone: true, selector: "app-input-password", providers: [
2748
+ {
2749
+ provide: NG_VALUE_ACCESSOR,
2750
+ useExisting: forwardRef(() => InputPasswordComponent),
2751
+ multi: true,
2752
+ },
2753
+ ], usesInheritance: true, ngImport: i0, template: "<nz-input-group [nzSuffix]=\"suffixTemplate\">\r\n <input\r\n nz-input\r\n [value]=\"value\"\r\n [type]=\"passwordVisible ? 'text' : 'password'\"\r\n autocomplete=\"new-password\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n (input)=\"handleChangeInputPassword($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n</nz-input-group>\r\n<ng-template #suffixTemplate>\r\n <span\r\n nz-icon\r\n [nzType]=\"passwordVisible ? 'eye-invisible' : 'eye'\"\r\n (click)=\"passwordVisible = !passwordVisible\"\r\n ></span>\r\n</ng-template>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: i1$1.NzInputGroupComponent, selector: "nz-input-group", inputs: ["nzAddOnBeforeIcon", "nzAddOnAfterIcon", "nzPrefixIcon", "nzSuffixIcon", "nzAddOnBefore", "nzAddOnAfter", "nzPrefix", "nzStatus", "nzSuffix", "nzSize", "nzSearch", "nzCompact"], exportAs: ["nzInputGroup"] }, { kind: "directive", type: i1$1.NzInputGroupWhitSuffixOrPrefixDirective, selector: "nz-input-group[nzSuffix], nz-input-group[nzPrefix]" }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }] }); }
2754
+ }
2755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputPasswordComponent, decorators: [{
2756
+ type: Component,
2757
+ args: [{ selector: "app-input-password", standalone: true, imports: [...ZORRO_MODULES$7], providers: [
2758
+ {
2759
+ provide: NG_VALUE_ACCESSOR,
2760
+ useExisting: forwardRef(() => InputPasswordComponent),
2761
+ multi: true,
2762
+ },
2763
+ ], template: "<nz-input-group [nzSuffix]=\"suffixTemplate\">\r\n <input\r\n nz-input\r\n [value]=\"value\"\r\n [type]=\"passwordVisible ? 'text' : 'password'\"\r\n autocomplete=\"new-password\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n (input)=\"handleChangeInputPassword($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n</nz-input-group>\r\n<ng-template #suffixTemplate>\r\n <span\r\n nz-icon\r\n [nzType]=\"passwordVisible ? 'eye-invisible' : 'eye'\"\r\n (click)=\"passwordVisible = !passwordVisible\"\r\n ></span>\r\n</ng-template>\r\n" }]
2764
+ }], ctorParameters: () => [] });
2765
+
2766
+ const ZORRO_MODULES$6 = [
2462
2767
  NzInputModule,
2463
2768
  NgxCurrencyDirective,
2464
2769
  CurrencyPipe,
2465
2770
  CommonModule,
2466
2771
  FormsModule,
2467
2772
  ];
2468
- const COMPONENTS$6 = [SuffixComponent];
2773
+ const COMPONENTS$3 = [SuffixComponent];
2469
2774
  class InputTextComponent extends BaseValueAccessor {
2470
2775
  constructor() {
2471
2776
  super();
2472
- this.fieldSignal = computed(() => {
2777
+ this.field = computed(() => {
2473
2778
  const base = this.fieldInput();
2474
2779
  if (!(base instanceof InputTextField) ||
2475
- (base.type !== FieldTypeEnum.InputText && base.controlType !== ControlTypeEnum.Input)) {
2476
- throw new Error('Expected InputTextField');
2780
+ (base.type !== FieldTypeEnum.InputText &&
2781
+ base.controlType !== ControlTypeEnum.Input)) {
2782
+ throw new Error("Expected InputTextField");
2477
2783
  }
2478
2784
  return base;
2479
2785
  });
2480
- this.inputDisplayValue = '';
2481
- this.displayNoValueText = NO_INFO_TEXT;
2482
- // 1. Effect chung Add + Edit
2483
- effect(() => {
2484
- if (this.isAddOrEdit()) {
2485
- const field = untracked(() => this.fieldSignal());
2486
- const { placeholder } = field;
2487
- if (!placeholder)
2488
- field.placeholder = createPlaceholder(field);
2489
- }
2490
- });
2491
- // Trigger reset từ bên ngoài form.
2492
- effect(() => {
2493
- const reset = this.dFormService.resetTrigger();
2494
- if (reset?.formId === this.formId) {
2495
- this.reset();
2496
- }
2786
+ this.viewDisplayValue = computed(() => {
2787
+ // 1. Nếu không phải View thì không cần quan tâm text này
2788
+ if (!this.isView())
2789
+ return "";
2790
+ // 2. Lấy giá trị từ valueSignal
2791
+ const val = this.valueSignal();
2792
+ return val ? val : NO_INFO_TEXT;
2497
2793
  });
2498
2794
  }
2795
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
2796
+ resolvePlaceholderText() {
2797
+ return createInputPlaceholder(this.field());
2798
+ }
2799
+ // Xử lý khi nhập giá trị mới vào input
2499
2800
  handleChangeInputText(event) {
2500
2801
  const inputValue = event.target.value;
2501
2802
  this.propagateValue(inputValue);
2502
2803
  // Gán data vào đầu ra của form.
2503
- this.fieldService.onFieldChange(this.fieldKey(), inputValue);
2504
2804
  this.patchFormData(this.fieldKey(), inputValue);
2505
2805
  }
2506
2806
  handleChangeFormValue(value) {
2507
2807
  const inputValue = value?.toString();
2508
2808
  this.propagateValue(inputValue);
2509
- this.fieldService.onFieldChange(this.fieldKey(), inputValue);
2510
2809
  this.patchFormData(this.fieldKey(), inputValue);
2511
2810
  }
2512
2811
  writeValueToView(value) {
2513
- if (this.isView()) {
2514
- this.inputDisplayValue = this.fieldSignal().modelValue || NO_INFO_TEXT;
2515
- return;
2516
- }
2517
2812
  if (!this.isAddOrEdit())
2518
2813
  return;
2519
2814
  // Gán data vào đầu ra của form.
@@ -2526,181 +2821,228 @@ class InputTextComponent extends BaseValueAccessor {
2526
2821
  useExisting: forwardRef(() => InputTextComponent),
2527
2822
  multi: true,
2528
2823
  },
2529
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n [(ngModel)]=\"inputDisplayValue\"\r\n class=\"modeView\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n />\r\n } @else {\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"modeView\" />\r\n }\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (fieldSignal().isCurrency) {\r\n <span class=\"modeView\">\r\n {{ inputDisplayValue | currency: 'VND' : 'symbol' : '1.0-0' : 'vi-VN' }}\r\n </span>\r\n } @else {\r\n <span class=\"modeView\">{{ inputDisplayValue }}</span>\r\n }\r\n }\r\n } @else {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n [(ngModel)]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (ngModelChange)=\"handleChangeFormValue($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n } @else {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [value]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.input-text-wrapper{display:flex}.modeView{pointer-events:none;color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "directive", type: NgxCurrencyDirective, selector: "input[currencyMask]", inputs: ["currencyMask", "options"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2824
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (field().viewMode === viewModeEnum.form) {\r\n @if (field().isCurrency) {\r\n <input\r\n nz-input\r\n [ngModel]=\"viewDisplayValue()\"\r\n class=\"modeView\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n />\r\n } @else {\r\n <input nz-input [value]=\"viewDisplayValue()\" class=\"modeView\" />\r\n }\r\n }\r\n @if (field().viewMode === viewModeEnum.text) {\r\n @if (field().isCurrency) {\r\n <span class=\"modeView\">\r\n {{\r\n viewDisplayValue() | currency: \"VND\" : \"symbol\" : \"1.0-0\" : \"vi-VN\"\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"modeView\">{{ viewDisplayValue() }}</span>\r\n }\r\n }\r\n } @else {\r\n @if (field().isCurrency) {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n [(ngModel)]=\"value\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (ngModelChange)=\"handleChangeFormValue($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n } @else {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [value]=\"value\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "directive", type: NgxCurrencyDirective, selector: "input[currencyMask]", inputs: ["currencyMask", "options"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2530
2825
  }
2531
2826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputTextComponent, decorators: [{
2532
2827
  type: Component,
2533
- args: [{ selector: 'app-input-text', standalone: true, imports: [...ZORRO_MODULES$a, ...COMPONENTS$6], providers: [
2828
+ args: [{ selector: "app-input-text", standalone: true, imports: [...ZORRO_MODULES$6, ...COMPONENTS$3], providers: [
2534
2829
  {
2535
2830
  provide: NG_VALUE_ACCESSOR,
2536
2831
  useExisting: forwardRef(() => InputTextComponent),
2537
2832
  multi: true,
2538
2833
  },
2539
- ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n [(ngModel)]=\"inputDisplayValue\"\r\n class=\"modeView\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n />\r\n } @else {\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"modeView\" />\r\n }\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (fieldSignal().isCurrency) {\r\n <span class=\"modeView\">\r\n {{ inputDisplayValue | currency: 'VND' : 'symbol' : '1.0-0' : 'vi-VN' }}\r\n </span>\r\n } @else {\r\n <span class=\"modeView\">{{ inputDisplayValue }}</span>\r\n }\r\n }\r\n } @else {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n [(ngModel)]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (ngModelChange)=\"handleChangeFormValue($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n } @else {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [value]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.input-text-wrapper{display:flex}.modeView{pointer-events:none;color:#575757;font-size:12px}\n"] }]
2834
+ ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (field().viewMode === viewModeEnum.form) {\r\n @if (field().isCurrency) {\r\n <input\r\n nz-input\r\n [ngModel]=\"viewDisplayValue()\"\r\n class=\"modeView\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n />\r\n } @else {\r\n <input nz-input [value]=\"viewDisplayValue()\" class=\"modeView\" />\r\n }\r\n }\r\n @if (field().viewMode === viewModeEnum.text) {\r\n @if (field().isCurrency) {\r\n <span class=\"modeView\">\r\n {{\r\n viewDisplayValue() | currency: \"VND\" : \"symbol\" : \"1.0-0\" : \"vi-VN\"\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"modeView\">{{ viewDisplayValue() }}</span>\r\n }\r\n }\r\n } @else {\r\n @if (field().isCurrency) {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n [(ngModel)]=\"value\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (ngModelChange)=\"handleChangeFormValue($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n } @else {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [value]=\"value\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n" }]
2540
2835
  }], ctorParameters: () => [] });
2541
2836
 
2542
- const ZORRO_MODULES$9 = [NzSelectModule, NzInputModule, NzSpinModule];
2543
- const COMPONENTS$5 = [SuffixComponent];
2544
- class MultiSelectComponent extends BaseValueAccessor {
2837
+ class BaseSelectComponent extends BaseValueAccessor {
2838
+ createDependencyStreams(dependsOn) {
2839
+ return dependsOn.map((depend) => this.fieldService
2840
+ .getDependValueChanges$(depend.fieldKey)
2841
+ .pipe(map((val) => ({ key: depend.paramKey, value: val }))));
2842
+ }
2843
+ evaluateDependencies(results, dependType) {
2844
+ const isValidValue = (val) => val !== null && val !== "" && val !== undefined;
2845
+ let isConditionMet = false;
2846
+ if (dependType === DependTypeEnum.Any) {
2847
+ isConditionMet = results.some((r) => isValidValue(r.value));
2848
+ }
2849
+ else {
2850
+ isConditionMet = results.every((r) => isValidValue(r.value));
2851
+ }
2852
+ const params = results.reduce((acc, curr) => ({ ...acc, [curr.key]: curr.value }), {});
2853
+ return { valid: isConditionMet, params };
2854
+ }
2545
2855
  constructor() {
2546
2856
  super();
2547
- this.fieldSignal = computed(() => {
2548
- const base = this.fieldInput();
2549
- if (!(base instanceof MultiSelectField) ||
2550
- (base.type !== FieldTypeEnum.MultiSelect && base.controlType !== ControlTypeEnum.Select)) {
2551
- throw new Error('Expected MultiSelectField');
2857
+ // Alias to strongly type the field input for Select base logic
2858
+ this.selectField = computed(() => this.fieldInput());
2859
+ this.isFirstLoad = true;
2860
+ // 1. Tạo stream params từ các field phụ thuộc
2861
+ this.dependParams$ = toObservable(this.selectField).pipe(switchMap((field) => {
2862
+ const { dependsOn, dependType } = field;
2863
+ if (!dependsOn || dependsOn.length === 0) {
2864
+ return of({ valid: true, params: {} });
2552
2865
  }
2553
- return base;
2866
+ const dependObs$ = this.createDependencyStreams(dependsOn);
2867
+ return combineLatest(dependObs$).pipe(map((results) => this.evaluateDependencies(results, dependType)));
2868
+ }));
2869
+ this.dependParamsSignal = toSignal(this.dependParams$, {
2870
+ initialValue: { valid: false, params: {} },
2554
2871
  });
2555
- this.displayNoValueText = NO_INFO_TEXT;
2556
- this.displayOptionsValue = [];
2557
- this.loading = signal(false);
2558
- this.options = [];
2559
- this.queryParams = {};
2560
- // 1. Effect chung Add + Edit
2561
- effect(() => {
2562
- if (this.isAddOrEdit()) {
2563
- const field = untracked(() => this.fieldSignal());
2564
- const { placeholder, dependsOn } = field;
2565
- // Nếu field không phụ thuộc vào field khác mặc định phải gọi
2566
- if (dependsOn?.length === 0)
2567
- this.getOptions();
2568
- if (!placeholder)
2569
- field.placeholder = createSelectPlaceholder(field);
2570
- }
2872
+ // 2. rxResource lấy danh sách
2873
+ this.optionsResource = rxResource({
2874
+ request: () => {
2875
+ if (this.isView())
2876
+ return undefined;
2877
+ const state = this.dependParamsSignal();
2878
+ if (!state || !state.valid)
2879
+ return undefined;
2880
+ return state.params;
2881
+ },
2882
+ loader: ({ request: params }) => {
2883
+ const field = untracked(() => this.selectField());
2884
+ const fullApiUrl = field?.apiConfig?.baseUrl || "";
2885
+ const apiParams = field?.apiConfig?.params || {};
2886
+ const rawParams = { ...apiParams, ...params };
2887
+ const finalParams = Object.fromEntries(Object.entries(rawParams).filter(([_, val]) => val !== null && val !== undefined && val !== ""));
2888
+ if (!fullApiUrl) {
2889
+ return of({
2890
+ code: 0,
2891
+ message: "Success",
2892
+ data: field?.options ?? [],
2893
+ });
2894
+ }
2895
+ return this.fieldService.getAllOptions(fullApiUrl, finalParams).pipe(catchError((error) => of({ code: -1, message: "Error", data: [] })));
2896
+ },
2571
2897
  });
2572
- // Trigger reset từ bên ngoài form.
2573
- effect(() => {
2574
- const reset = this.dFormService.resetTrigger();
2575
- if (reset?.formId === this.formId) {
2576
- this.reset();
2898
+ // Gom chung loading từ rxResource isDisabledSignal
2899
+ this.disabled = computed(() => this.optionsResource.isLoading() || this.disabledSignal());
2900
+ // 3. Xào nấu Options (Prepend, Append, Filter)
2901
+ this.options = computed(() => {
2902
+ const state = this.dependParamsSignal();
2903
+ const isLoading = this.optionsResource.isLoading();
2904
+ const res = this.optionsResource.value();
2905
+ if (!state || !state.valid || isLoading || !res || res.code !== 0) {
2906
+ return [];
2577
2907
  }
2908
+ const field = this.selectField();
2909
+ const { optionsToPrepend, optionsToAppend, exceptOptions } = field;
2910
+ const rawData = res.data || [];
2911
+ return [...optionsToPrepend, ...rawData, ...optionsToAppend].filter((opt) => !exceptOptions.some((ex) => ex[field.bindValue] === opt[field.bindValue]));
2578
2912
  });
2579
- // Xử luồng ban đầu cho việc getOptions, cũng như getOptions phụ thuộc vào dependsOn.
2913
+ // 4. Effect xử toàn bộ logic Form Control (Disable/Enable/Reset)
2580
2914
  effect(() => {
2581
- const { canProceed, queryParams } = this.resolveDependencyState();
2582
- if (!canProceed) {
2583
- this.disableAndResetControl();
2584
- return;
2585
- }
2586
- this.queryParams = { ...this.queryParams, ...queryParams };
2587
- this.getOptions();
2588
- this.enableControl();
2915
+ const state = this.dependParamsSignal();
2916
+ const isLoading = this.optionsResource.isLoading();
2917
+ const res = this.optionsResource.value();
2918
+ untracked(() => {
2919
+ // CASE 1 & 2: CHƯA ĐỦ ĐIỀU KIỆN HOẶC ĐANG GỌI API -> Disable và Reset
2920
+ if (!state || !state.valid || isLoading) {
2921
+ this.disableControl();
2922
+ if (!this.isFirstLoad) {
2923
+ this.resetControl();
2924
+ }
2925
+ this.isFirstLoad = false;
2926
+ return;
2927
+ }
2928
+ // CASE 3: API ĐÃ LOAD XONG
2929
+ if (res) {
2930
+ if (res.code !== 0) {
2931
+ this.disableAndResetControl();
2932
+ return;
2933
+ }
2934
+ const field = this.selectField();
2935
+ if (!field.isDisabled) {
2936
+ this.enableControl();
2937
+ }
2938
+ // Móc nối cho các class con có nhu cầu đồng bộ data sau khi load API
2939
+ this.onApiOptionsLoaded();
2940
+ }
2941
+ });
2589
2942
  });
2590
2943
  }
2591
- // Xử tạo queryParams cho phép gọi getOptions hay không.
2592
- resolveDependencyState() {
2593
- //1. Không gọi API lấy options nếu view mode.
2594
- if (untracked(() => this.isView())) {
2595
- return {
2596
- canProceed: false,
2597
- queryParams: {},
2598
- };
2599
- }
2600
- const field = untracked(() => this.fieldSignal());
2601
- const { dependsOn, dependType } = field;
2602
- // Không phụ thuộc vào giá trị từ field khác, tức mảng dependsOn rỗng -> luôn cho phép gọi list.
2603
- const requiredCount = dependsOn?.length ?? 0;
2604
- if (requiredCount === 0) {
2605
- return {
2606
- canProceed: true,
2607
- queryParams: {},
2608
- };
2609
- }
2610
- // 3. Tạo mảng object dependValues theo paramKey và value. Ví dụ [{paramKey: 'provinceCode', value: '36'}]
2611
- const dependValuesMap = this.fieldService.getDependValuesMap();
2612
- const dependValues = dependsOn.map(depend => {
2613
- const getter = dependValuesMap.get(depend.fieldKey);
2614
- const value = typeof getter === 'function' ? getter() : getter;
2615
- return { paramKey: depend.paramKey, value };
2944
+ // Cho phép component con overide nếu cần (ví dụ để hydrate sourceObject)
2945
+ onApiOptionsLoaded() { }
2946
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: BaseSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2947
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.15", type: BaseSelectComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
2948
+ }
2949
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: BaseSelectComponent, decorators: [{
2950
+ type: Directive
2951
+ }], ctorParameters: () => [] });
2952
+
2953
+ const ZORRO_MODULES$5 = [NzSelectModule, NzInputModule, NzSpinModule];
2954
+ const COMPONENTS$2 = [SuffixComponent];
2955
+ class MultiSelectComponent extends BaseSelectComponent {
2956
+ constructor() {
2957
+ super();
2958
+ this.field = computed(() => {
2959
+ const base = this.fieldInput();
2960
+ if (!(base instanceof MultiSelectField) ||
2961
+ (base.type !== FieldTypeEnum.MultiSelect &&
2962
+ base.controlType !== ControlTypeEnum.Select)) {
2963
+ throw new Error("Expected MultiSelectField");
2964
+ }
2965
+ return base;
2966
+ });
2967
+ this.displayNoValueText = NO_INFO_TEXT;
2968
+ // Biến đổi dữ liệu hiển thị ở chế độ View
2969
+ this.viewDisplayValue = computed(() => {
2970
+ if (!this.isView())
2971
+ return [];
2972
+ console.log("viewDisplayValue", this.field().modelValue || []);
2973
+ return this.getDisplayValue(this.field().modelValue || []);
2616
2974
  });
2617
- // 4. Đếm số paramKey giá trị đã có (không phải falsy).
2618
- const availableCount = dependValues.filter(v => v.value).length;
2619
- // 5. Check xem có thể tiếp tục đến gọi list không.
2620
- // ===== Nếu dependType là 'all' thì canProceed true chỉ khi availableCount = với requiredCount.
2621
- // ===== Ngược lại dependType là 'any' chỉ cần availableCount > 0.
2622
- const canProceed = dependType === DependTypeEnum.All ? availableCount === requiredCount : availableCount > 0;
2623
- return {
2624
- canProceed,
2625
- queryParams: Object.fromEntries(dependValues.map(v => [v.paramKey, v.value])),
2626
- };
2627
- }
2628
- createFullApiUrl(field) {
2629
- let fullApiUrl = field?.apiConfig?.baseUrl || '';
2630
- const params = field?.apiConfig?.params || {};
2631
- this.queryParams = {
2632
- ...this.queryParams,
2633
- ...params,
2634
- };
2635
- return fullApiUrl;
2636
2975
  }
2637
- // Lấy danh sách options từ GET api.
2638
- getOptions() {
2639
- const field = this.fieldSignal();
2640
- const fullApiUrl = this.createFullApiUrl(field);
2641
- if (!fullApiUrl) {
2642
- this.options = field?.options ?? [];
2643
- return;
2976
+ onApiOptionsLoaded() {
2977
+ const field = this.field();
2978
+ if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
2979
+ const newOutputValue = this.value && this.getValueByOutputAs(this.value);
2980
+ this.patchFormData(this.fieldKey(), newOutputValue);
2644
2981
  }
2645
- this.loading.set(true);
2646
- this.disableControl();
2647
- this.fieldService
2648
- .getAllOptions(fullApiUrl, this.queryParams)
2649
- .pipe(finalize(() => {
2650
- this.loading.set(false);
2651
- this.enableControl();
2652
- }), this.takeUntilDestroy())
2653
- .subscribe({
2654
- next: (res) => {
2655
- if (res?.code === 0) {
2656
- this.options = res?.data;
2657
- }
2658
- },
2659
- error: (error) => { },
2660
- });
2982
+ }
2983
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
2984
+ resolvePlaceholderText() {
2985
+ return createMultiSelectPlaceholder(this.field());
2661
2986
  }
2662
2987
  handleChangeOptions(optionValue) {
2663
2988
  this.propagateValue(optionValue);
2664
2989
  }
2665
2990
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
2666
2991
  writeValueToView(value) {
2667
- if (this.isView()) {
2668
- const modelValue = this.fieldSignal().modelValue;
2669
- if (modelValue)
2670
- this.displayOptionsValue = this.getDisplayValue(modelValue);
2671
- return;
2672
- }
2673
2992
  if (!this.isAddOrEdit())
2674
2993
  return;
2994
+ this.fieldService.emitDependValueChange(this.fieldKey(), value.length ? value.join(",") : "");
2675
2995
  // Gán data vào đầu ra của form.
2676
2996
  const newOutputValue = value && this.getValueByOutputAs(value);
2677
2997
  this.patchFormData(this.fieldKey(), newOutputValue);
2678
2998
  }
2679
2999
  // Xử lý phụ thuộc khi change value
2680
3000
  handleValueChange(value) {
3001
+ const { isParent } = this.field();
3002
+ if (isParent) {
3003
+ console.log("cscs");
3004
+ this.fieldService.emitDependValueChange(this.fieldKey(), value.length ? value.join(",") : "");
3005
+ }
2681
3006
  const newOutputValue = value && this.getValueByOutputAs(value);
2682
3007
  this.patchFormData(this.fieldKey(), newOutputValue);
2683
3008
  }
2684
3009
  // Xử lý output value đầu ra theo outputAs mà người dùng mong muốn.
2685
3010
  getValueByOutputAs(singleValueArr) {
2686
- const field = this.fieldSignal();
3011
+ const field = this.field();
3012
+ const { outputAs, bindValue, modelValue } = field;
2687
3013
  if (singleValueArr.length === 0) {
2688
3014
  return singleValueArr.map(String);
2689
3015
  }
2690
3016
  // Nếu outputAs là dạng single.
2691
3017
  let value = singleValueArr.map(String);
2692
3018
  // Nếu outputAs là dạng object.
2693
- if (field.outputAs === SelectOutputValueTypeEnum.Object) {
2694
- value = value.map(val => ({
2695
- [field.bindValue]: val,
3019
+ if (outputAs === SelectOutputValueTypeEnum.Object) {
3020
+ value = value.map((val) => ({
3021
+ [bindValue]: val,
2696
3022
  }));
2697
3023
  }
3024
+ // Nếu outputAs là dạng SourceObject.
3025
+ if (outputAs === SelectOutputValueTypeEnum.SourceObject) {
3026
+ const options = untracked(() => this.options());
3027
+ value = singleValueArr.map((val) => {
3028
+ // 1. Tìm trong options hiện tại
3029
+ let foundValue = options.find((opt) => opt[bindValue] === val);
3030
+ // 2. Fallback tìm trong modelValue (vì modelValue có thể là array of objects)
3031
+ // if (!foundValue && modelValue && Array.isArray(modelValue)) {
3032
+ // foundValue = modelValue.find(
3033
+ // (mOpt: any) => typeof mOpt === "object" && mOpt[bindValue] === val
3034
+ // );
3035
+ // }
3036
+ // 3. Fallback cuối cùng
3037
+ return foundValue ?? { [bindValue]: val };
3038
+ });
3039
+ }
2698
3040
  return value;
2699
3041
  }
2700
3042
  // Làm phẳng giá trị để hiện thị ở chế độ View.
2701
3043
  getDisplayValue(value) {
2702
3044
  const optionsValue = value.map((val) => {
2703
- if (typeof val === 'object') {
3045
+ if (typeof val === "object") {
2704
3046
  return this.getCustomLabel(val);
2705
3047
  }
2706
3048
  return val;
@@ -2708,7 +3050,7 @@ class MultiSelectComponent extends BaseValueAccessor {
2708
3050
  return optionsValue;
2709
3051
  }
2710
3052
  transformOptionValue(option) {
2711
- const field = this.fieldSignal();
3053
+ const field = this.field();
2712
3054
  let optionValue;
2713
3055
  optionValue = option[field.bindValue];
2714
3056
  return optionValue;
@@ -2718,15 +3060,21 @@ class MultiSelectComponent extends BaseValueAccessor {
2718
3060
  }
2719
3061
  // Biến đổi label nếu có thuộc tính hàm biến đổi transformLabel.
2720
3062
  getCustomLabel(option) {
2721
- const field = this.fieldSignal();
2722
- if (typeof option[field.bindLabel] === 'function' || !option[field.bindLabel]) {
2723
- let warnMessage = 'Giá trị label lấy theo bindLabel hiện đang là dạng object, array hoặc các dạng còn lại của complex data';
2724
- if (!option[field.bindLabel])
2725
- warnMessage = 'Không tồn tại giá trị label khi lấy theo bindLabel';
2726
- //console.warn(warnMessage);
3063
+ const field = this.field();
3064
+ // 1. Ưu tiên lấy bindLabel, nếu không có thì fallback sang bindValue
3065
+ let rawLabel = option[field.bindLabel];
3066
+ if (rawLabel === undefined || rawLabel === null || rawLabel === "") {
3067
+ rawLabel = option[field.bindValue];
3068
+ }
3069
+ // 2. Kiểm tra nếu không tồn tại hoặc là dạng object/function (kể cả array)
3070
+ if (rawLabel === undefined ||
3071
+ rawLabel === null ||
3072
+ typeof rawLabel === "object" ||
3073
+ typeof rawLabel === "function") {
2727
3074
  return NO_INFO_TEXT;
2728
3075
  }
2729
- let optionLabel = option[field.bindLabel];
3076
+ let optionLabel = String(rawLabel);
3077
+ // 3. Biến đổi theo transformLabel nếu có
2730
3078
  if (field.transformLabel) {
2731
3079
  const tranformLabelValue = field.transformLabel(option);
2732
3080
  if (tranformLabelValue)
@@ -2741,194 +3089,101 @@ class MultiSelectComponent extends BaseValueAccessor {
2741
3089
  useExisting: forwardRef(() => MultiSelectComponent),
2742
3090
  multi: true,
2743
3091
  },
2744
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [(ngModel)]=\"displayOptionsValue\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of displayOptionsValue; track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\"> </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (displayOptionsValue && displayOptionsValue.length) {\r\n <ul class=\"view-list\">\r\n @for (label of displayOptionsValue; track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"loading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important;font-size:12px}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
3092
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n @if (field().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [ngModel]=\"viewDisplayValue()\"\r\n [nzMaxTagCount]=\"field().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"field().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of viewDisplayValue(); track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (viewMode() === viewModeEnum.text) {\r\n @if (viewDisplayValue().length) {\r\n <ul class=\"view-list\">\r\n @for (label of viewDisplayValue(); track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"disabled()\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzMaxTagCount]=\"field().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"field().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"optionsResource.isLoading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (\r\n option of options();\r\n track `${option[this.field().bindValue]}-${$index}`\r\n ) {\r\n <nz-option\r\n [nzValue]=\"transformOptionValue(option)\"\r\n [nzLabel]=\"getOptionLabel(option)\"\r\n >\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2745
3093
  }
2746
3094
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiSelectComponent, decorators: [{
2747
3095
  type: Component,
2748
- args: [{ selector: 'app-multi-select', standalone: true, imports: [...CORE_MODULES, ...FORM_MODULES, ...ZORRO_MODULES$9, ...COMPONENTS$5], providers: [
3096
+ args: [{ selector: "app-multi-select", standalone: true, imports: [...CORE_MODULES, ...FORM_MODULES, ...ZORRO_MODULES$5, ...COMPONENTS$2], providers: [
2749
3097
  {
2750
3098
  provide: NG_VALUE_ACCESSOR,
2751
3099
  useExisting: forwardRef(() => MultiSelectComponent),
2752
3100
  multi: true,
2753
3101
  },
2754
- ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [(ngModel)]=\"displayOptionsValue\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of displayOptionsValue; track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\"> </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (displayOptionsValue && displayOptionsValue.length) {\r\n <ul class=\"view-list\">\r\n @for (label of displayOptionsValue; track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"loading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important;font-size:12px}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757;font-size:12px}\n"] }]
3102
+ ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n @if (field().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [ngModel]=\"viewDisplayValue()\"\r\n [nzMaxTagCount]=\"field().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"field().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of viewDisplayValue(); track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (viewMode() === viewModeEnum.text) {\r\n @if (viewDisplayValue().length) {\r\n <ul class=\"view-list\">\r\n @for (label of viewDisplayValue(); track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"disabled()\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzMaxTagCount]=\"field().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"field().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"optionsResource.isLoading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (\r\n option of options();\r\n track `${option[this.field().bindValue]}-${$index}`\r\n ) {\r\n <nz-option\r\n [nzValue]=\"transformOptionValue(option)\"\r\n [nzLabel]=\"getOptionLabel(option)\"\r\n >\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757}\n"] }]
2755
3103
  }], ctorParameters: () => [] });
2756
3104
 
2757
- const ZORRO_MODULES$8 = [NzSelectModule, NzSpinModule, NzInputModule];
2758
- const COMPONENTS$4 = [SuffixComponent];
2759
- class SingleSelectComponent extends BaseValueAccessor {
3105
+ const modules$3 = [CommonModule, FormsModule, ReactiveFormsModule];
3106
+ const zorroModules$3 = [NzSelectModule, NzSpinModule, NzInputModule];
3107
+ const components$2 = [SuffixComponent];
3108
+ class SingleSelectComponent extends BaseSelectComponent {
2760
3109
  constructor() {
2761
3110
  super();
2762
- this.fieldSignal = computed(() => {
3111
+ this.field = computed(() => {
2763
3112
  const base = this.fieldInput();
2764
3113
  if (!(base instanceof SingleSelectField) ||
2765
- (base.type !== FieldTypeEnum.SingleSelect && base.controlType !== ControlTypeEnum.Select)) {
2766
- throw new Error('Expected SingleSelectField');
3114
+ (base.type !== FieldTypeEnum.SingleSelect &&
3115
+ base.controlType !== ControlTypeEnum.Select)) {
3116
+ throw new Error("Expected SingleSelectField");
2767
3117
  }
2768
3118
  return base;
2769
3119
  });
2770
3120
  // Giá trị dùng hiển thị ở chế độ View.
2771
- this.displayValue = '';
2772
- this.loadingSignal = signal(false);
2773
- this.isDisabledSignal = signal(false);
2774
- this.disabled = computed(() => this.loadingSignal() || this.isDisabledSignal());
2775
- this.options = [];
2776
- this.queryParams = {};
2777
- // 1. Effect chung Add + Edit
2778
- effect(() => {
2779
- if (this.isAddOrEdit()) {
2780
- const field = untracked(() => this.fieldSignal());
2781
- const { placeholder } = field;
2782
- if (!placeholder)
2783
- field.placeholder = createSelectPlaceholder(field);
2784
- }
2785
- });
2786
- // Trigger reset từ bên ngoài form.
2787
- effect(() => {
2788
- const reset = this.dFormService.resetTrigger();
2789
- if (reset?.formId === this.formId) {
2790
- this.reset();
2791
- }
2792
- });
2793
- effect(() => {
2794
- if (untracked(() => this.isView()))
2795
- return;
2796
- const { key, dependsOn, isParent, dependType } = untracked(() => this.fieldSignal());
2797
- // Không dính dáng gì đến isParent và dependsOn.
2798
- if (!isParent && dependsOn.length === 0) {
2799
- this.getOptions();
2800
- return;
2801
- }
2802
- const dependValuesMap = this.fieldService.getDependValuesMap();
2803
- const dependValues = dependsOn?.map(depend => {
2804
- const dependFunc = dependValuesMap.get(depend.fieldKey);
2805
- const value = typeof dependFunc === 'function' ? dependFunc() : dependFunc;
2806
- return [depend.paramKey, value];
2807
- });
2808
- // console.log(`${key} dependValue`, dependValues);
2809
- const params = Object.fromEntries(dependValues);
2810
- this.queryParams = {
2811
- ...this.queryParams,
2812
- ...params,
2813
- };
2814
- // console.log(`${key} queryParams`, this.queryParams);
2815
- // Nếu tất cả các phụ thuộc có value thì mới gọi API.
2816
- const allDependHasValues = dependValues?.every(([fieldKey, value]) => value);
2817
- // console.log(`${key} allDependHasValues`, allDependHasValues);
2818
- if (allDependHasValues) {
2819
- // this.isDisabledSignal.set(false);
2820
- this.control?.enable();
2821
- this.getOptions();
2822
- }
2823
- else {
2824
- // Ngược lại reset và disabled.
2825
- this.control?.reset();
2826
- // this.isDisabledSignal.set(true);
2827
- this.control?.disable();
2828
- }
3121
+ this.viewDisplayValue = computed(() => {
3122
+ if (!this.isView())
3123
+ return "";
3124
+ return this.getDisplayValue(this.field().modelValue || this.valueSignal());
2829
3125
  });
2830
3126
  }
3127
+ onApiOptionsLoaded() {
3128
+ const field = this.field();
3129
+ if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
3130
+ const newOutputValue = this.value && this.getValueByOutputAs(this.value);
3131
+ this.patchFormData(this.fieldKey(), newOutputValue);
3132
+ }
3133
+ }
3134
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
3135
+ resolvePlaceholderText() {
3136
+ return createSingleSelectPlaceholder(this.field());
3137
+ }
2831
3138
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
2832
3139
  writeValueToView(value) {
2833
- if (this.isView()) {
2834
- this.displayValue = this.getDisplayValue(this.fieldSignal().modelValue);
2835
- return;
2836
- }
2837
3140
  if (!this.isAddOrEdit())
2838
3141
  return;
2839
- this.fieldService.updateDependValuesMap(this.fieldKey(), value ?? '');
2840
- // const signal = untracked(() => this.fieldService.getDependValuesMap().get(this.fieldKey()))
2841
- // console.log("DependValuesMap SET", untracked(() => signal!()));
3142
+ // Bắn giá trị mới vào BehaviorSubject của FieldService.
3143
+ // Việc này báo cho các component Con biết Parent vừa thay đổi data.
3144
+ // Lợi ích: Dòng data chạy thẳng đứng (Push) vào ống, không phụ thuộc vào Signal tracking ẩn.
3145
+ this.fieldService.emitDependValueChange(this.fieldKey(), value ?? "");
2842
3146
  // Gán data vào đầu ra của form.
2843
3147
  const newOutputValue = value && this.getValueByOutputAs(value);
2844
3148
  this.patchFormData(this.fieldKey(), newOutputValue);
2845
3149
  }
2846
3150
  // Xử lý phụ thuộc khi change value
2847
3151
  handleValueChange(value) {
2848
- const { isParent } = this.fieldSignal();
3152
+ const { isParent } = this.field();
2849
3153
  if (isParent) {
2850
- this.fieldService.updateDependValuesMap(this.fieldKey(), value);
2851
- // const signal = untracked(() => this.fieldService.getDependValuesMap().get(this.fieldKey()))
2852
- // console.log("DependValuesMap UI", untracked(() => signal!()));
3154
+ this.fieldService.emitDependValueChange(this.fieldKey(), value);
2853
3155
  }
2854
3156
  // Gán data vào đầu ra của form.
2855
3157
  const newOutputValue = value && this.getValueByOutputAs(value);
2856
3158
  this.patchFormData(this.fieldKey(), newOutputValue);
2857
3159
  }
2858
- createFullApiUrl() {
2859
- const field = untracked(() => this.fieldSignal());
2860
- let fullApiUrl = field?.apiConfig?.baseUrl || '';
2861
- const params = field?.apiConfig?.params || {};
2862
- this.queryParams = {
2863
- ...this.queryParams,
2864
- ...params,
2865
- };
2866
- return fullApiUrl;
2867
- }
2868
- // Lấy danh sách options từ GET api.
2869
- getOptions() {
2870
- const field = untracked(() => this.fieldSignal());
2871
- const { optionsToPrepend, optionsToAppend, exceptOptions } = field;
2872
- const fullApiUrl = this.createFullApiUrl();
2873
- if (!fullApiUrl) {
2874
- this.options = field?.options ?? [];
2875
- this.options = [...optionsToPrepend, ...this.options, ...optionsToAppend].filter(options => !exceptOptions.some(opts => opts[field.bindValue] === options[field.bindValue]));
2876
- return;
2877
- }
2878
- this.loadingSignal.set(true);
2879
- this.control?.disable();
2880
- this.fieldService
2881
- .getAllOptions(fullApiUrl, this.queryParams)
2882
- .pipe(this.takeUntilDestroy())
2883
- .subscribe({
2884
- next: (res) => {
2885
- this.loadingSignal.set(false);
2886
- if (!this.fieldSignal().isDisabled)
2887
- this.control?.enable();
2888
- if (res?.code === 0) {
2889
- this.options = res?.data;
2890
- this.options = [...optionsToPrepend, ...this.options, ...optionsToAppend].filter(options => !exceptOptions.some(opts => opts[field.bindValue] === options[field.bindValue]));
2891
- // Nếu outputAs là dạng sourceObject đồng bộ sau khi load xong list.
2892
- if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
2893
- const newOutputValue = this.options.find(option => option[field.bindValue] === this.value) ?? {
2894
- [field.bindValue]: this.value,
2895
- };
2896
- this.patchFormData(this.fieldKey(), newOutputValue);
2897
- }
2898
- }
2899
- },
2900
- error: (error) => {
2901
- this.loadingSignal.set(false);
2902
- if (!this.fieldSignal().isDisabled)
2903
- this.control?.enable();
2904
- },
2905
- });
2906
- }
2907
3160
  handleChangeOptions(optionValue) {
2908
- const { enableFieldChange, bindValue } = untracked(() => this.fieldSignal());
3161
+ const { enableFieldChange, bindValue } = untracked(() => this.field());
2909
3162
  if (enableFieldChange) {
2910
- const option = this.options.find(option => option[bindValue] === optionValue);
3163
+ const option = this.options().find((option) => option[bindValue] === optionValue);
2911
3164
  this.fieldService.onFieldChange(this.fieldKey(), option);
2912
3165
  }
2913
3166
  this.propagateValue(optionValue);
2914
3167
  }
2915
3168
  // Xử lý output value đầu ra theo outputAs mà người dùng mong muốn.
2916
3169
  getValueByOutputAs(singleValue) {
2917
- const field = this.fieldSignal();
3170
+ const { outputAs, bindValue, bindLabel, modelValue } = this.field();
2918
3171
  if (singleValue) {
2919
3172
  // Nếu outputAs là dạng single.
2920
3173
  let value = singleValue;
2921
3174
  // Nếu outputAs là dạng object.
2922
- if (field.outputAs === SelectOutputValueTypeEnum.Object) {
3175
+ if (outputAs === SelectOutputValueTypeEnum.Object) {
2923
3176
  value = {
2924
- [field.bindValue]: singleValue,
3177
+ [bindValue]: singleValue,
2925
3178
  };
2926
3179
  }
2927
3180
  // Nếu outputAs là dạng sourceObject.
2928
- if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
2929
- value = this.options.find(option => option[field.bindValue] === singleValue) ?? {
2930
- [field.bindValue]: singleValue,
2931
- };
3181
+ if (outputAs === SelectOutputValueTypeEnum.SourceObject) {
3182
+ value =
3183
+ this.options().find((option) => option[bindValue] === singleValue) ??
3184
+ (modelValue && typeof modelValue === "object"
3185
+ ? modelValue
3186
+ : { [bindValue]: value });
2932
3187
  }
2933
3188
  return value;
2934
3189
  }
@@ -2938,14 +3193,14 @@ class SingleSelectComponent extends BaseValueAccessor {
2938
3193
  getDisplayValue(value) {
2939
3194
  if (!value)
2940
3195
  return NO_INFO_TEXT;
2941
- if (typeof value === 'object') {
3196
+ if (typeof value === "object") {
2942
3197
  return this.getCustomLabel(value);
2943
3198
  }
2944
3199
  return value;
2945
3200
  }
2946
3201
  // Lấy value ở option tag.
2947
3202
  transformOptionValue(option) {
2948
- const field = this.fieldSignal();
3203
+ const field = this.field();
2949
3204
  let optionValue;
2950
3205
  optionValue = option[field.bindValue];
2951
3206
  return optionValue;
@@ -2956,15 +3211,21 @@ class SingleSelectComponent extends BaseValueAccessor {
2956
3211
  }
2957
3212
  // Biến đổi label nếu có thuộc tính hàm biến đổi transformLabel.
2958
3213
  getCustomLabel(option) {
2959
- const field = this.fieldSignal();
2960
- if (typeof option[field.bindLabel] === 'function' || !option[field.bindLabel]) {
2961
- let warnMessage = 'Giá trị label lấy theo bindLabel hiện đang là dạng object, array hoặc các dạng còn lại của complex data';
2962
- if (!option[field.bindLabel])
2963
- warnMessage = 'Không tồn tại giá trị label khi lấy theo bindLabel';
2964
- // console.warn(warnMessage);
3214
+ const field = this.field();
3215
+ // 1. Ưu tiên lấy bindLabel, nếu không có thì fallback sang bindValue
3216
+ let rawLabel = option[field.bindLabel];
3217
+ if (rawLabel === undefined || rawLabel === null || rawLabel === "") {
3218
+ rawLabel = option[field.bindValue];
3219
+ }
3220
+ // 2. Kiểm tra nếu không tồn tại hoặc là dạng object/function (kể cả array)
3221
+ if (rawLabel === undefined ||
3222
+ rawLabel === null ||
3223
+ typeof rawLabel === "object" ||
3224
+ typeof rawLabel === "function") {
2965
3225
  return NO_INFO_TEXT;
2966
3226
  }
2967
- let optionLabel = option[field.bindLabel];
3227
+ let optionLabel = String(rawLabel);
3228
+ // 3. Biến đổi theo transformLabel nếu có
2968
3229
  if (field.transformLabel) {
2969
3230
  const tranformLabelValue = field.transformLabel(option);
2970
3231
  if (tranformLabelValue)
@@ -2979,36 +3240,37 @@ class SingleSelectComponent extends BaseValueAccessor {
2979
3240
  useExisting: forwardRef(() => SingleSelectComponent),
2980
3241
  multi: true,
2981
3242
  },
2982
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input [title]=\"displayValue\" nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span [title]=\"displayValue\" class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzSuffixIcon]=\"loadingSignal() ? loadingIcon : null\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
3243
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (isView()) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input\r\n [title]=\"viewDisplayValue()\"\r\n nz-input\r\n [value]=\"viewDisplayValue()\"\r\n class=\"modeView\"\r\n />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span [title]=\"viewDisplayValue()\" class=\"modeView break-work\">{{\r\n viewDisplayValue()\r\n }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"disabled()\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzSuffixIcon]=\"optionsResource.isLoading() ? loadingIcon : null\"\r\n [title]=\"field().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"field().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 min-w-0\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (\r\n option of options();\r\n track `${option[this.field().bindValue]}-${$index}`\r\n ) {\r\n <nz-option\r\n [nzValue]=\"transformOptionValue(option)\"\r\n [nzLabel]=\"getOptionLabel(option)\"\r\n >\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2983
3244
  }
2984
3245
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SingleSelectComponent, decorators: [{
2985
3246
  type: Component,
2986
- args: [{ selector: 'app-single-select', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$8, ...COMPONENTS$4], providers: [
3247
+ args: [{ selector: "app-single-select", standalone: true, imports: [...modules$3, ...zorroModules$3, ...components$2], providers: [
2987
3248
  {
2988
3249
  provide: NG_VALUE_ACCESSOR,
2989
3250
  useExisting: forwardRef(() => SingleSelectComponent),
2990
3251
  multi: true,
2991
3252
  },
2992
- ], template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input [title]=\"displayValue\" nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span [title]=\"displayValue\" class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzSuffixIcon]=\"loadingSignal() ? loadingIcon : null\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"] }]
3253
+ ], template: "<div class=\"d-flex\">\r\n @if (isView()) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input\r\n [title]=\"viewDisplayValue()\"\r\n nz-input\r\n [value]=\"viewDisplayValue()\"\r\n class=\"modeView\"\r\n />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span [title]=\"viewDisplayValue()\" class=\"modeView break-work\">{{\r\n viewDisplayValue()\r\n }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"disabled()\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [nzSuffixIcon]=\"optionsResource.isLoading() ? loadingIcon : null\"\r\n [title]=\"field().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"field().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 min-w-0\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (\r\n option of options();\r\n track `${option[this.field().bindValue]}-${$index}`\r\n ) {\r\n <nz-option\r\n [nzValue]=\"transformOptionValue(option)\"\r\n [nzLabel]=\"getOptionLabel(option)\"\r\n >\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n" }]
2993
3254
  }], ctorParameters: () => [], propDecorators: { isCustomInputText: [{
2994
3255
  type: Input
2995
3256
  }] } });
2996
3257
 
2997
- const ZORRO_MODULES$7 = [NzTreeSelectModule, NzSpinModule, NzInputModule];
2998
- const COMPONENTS$3 = [SuffixComponent];
3258
+ const ZORRO_MODULES$4 = [NzTreeSelectModule, NzSpinModule, NzInputModule];
3259
+ const COMPONENTS$1 = [SuffixComponent];
2999
3260
  class TreeSelectComponent extends BaseValueAccessor {
3000
3261
  constructor() {
3001
3262
  super();
3002
- this.fieldSignal = computed(() => {
3263
+ this.field = computed(() => {
3003
3264
  const base = this.fieldInput();
3004
3265
  if (!(base instanceof TreeSelectField) ||
3005
- (base.type !== FieldTypeEnum.TreeSelect && base.controlType !== ControlTypeEnum.Select)) {
3006
- throw new Error('Expected TreeSelectField');
3266
+ (base.type !== FieldTypeEnum.TreeSelect &&
3267
+ base.controlType !== ControlTypeEnum.Select)) {
3268
+ throw new Error("Expected TreeSelectField");
3007
3269
  }
3008
3270
  return base;
3009
3271
  });
3010
3272
  // Giá trị dùng hiển thị ở chế độ View.
3011
- this.displayValue = '';
3273
+ this.displayValue = "";
3012
3274
  this.loadingSignal = signal(false);
3013
3275
  this.isDisabledSignal = signal(false);
3014
3276
  this.disabled = computed(() => this.loadingSignal() || this.isDisabledSignal());
@@ -3017,7 +3279,7 @@ class TreeSelectComponent extends BaseValueAccessor {
3017
3279
  // 1. Effect chung Add + Edit
3018
3280
  effect(() => {
3019
3281
  if (this.isAddOrEdit()) {
3020
- const field = untracked(() => this.fieldSignal());
3282
+ const field = untracked(() => this.field());
3021
3283
  const { placeholder } = field;
3022
3284
  if (!placeholder)
3023
3285
  field.placeholder = createSelectPlaceholder(field);
@@ -3033,16 +3295,16 @@ class TreeSelectComponent extends BaseValueAccessor {
3033
3295
  effect(() => {
3034
3296
  if (untracked(() => this.isView()))
3035
3297
  return;
3036
- const { key, dependsOn, isParent, dependType } = untracked(() => this.fieldSignal());
3298
+ const { key, dependsOn, isParent, dependType } = untracked(() => this.field());
3037
3299
  // Không dính dáng gì đến isParent và dependsOn.
3038
3300
  if (!isParent && dependsOn.length === 0) {
3039
3301
  this.getOptions();
3040
3302
  return;
3041
3303
  }
3042
3304
  const dependValuesMap = this.fieldService.getDependValuesMap();
3043
- const dependValues = dependsOn?.map(depend => {
3305
+ const dependValues = dependsOn?.map((depend) => {
3044
3306
  const dependFunc = dependValuesMap.get(depend.fieldKey);
3045
- const value = typeof dependFunc === 'function' ? dependFunc() : dependFunc;
3307
+ const value = typeof dependFunc === "function" ? dependFunc() : dependFunc;
3046
3308
  return [depend.paramKey, value];
3047
3309
  });
3048
3310
  // console.log(`${key} dependValue`, dependValues);
@@ -3068,15 +3330,19 @@ class TreeSelectComponent extends BaseValueAccessor {
3068
3330
  }
3069
3331
  });
3070
3332
  }
3333
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
3334
+ resolvePlaceholderText() {
3335
+ return createSingleSelectPlaceholder(this.field());
3336
+ }
3071
3337
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
3072
3338
  writeValueToView(value) {
3073
3339
  if (this.isView()) {
3074
- this.displayValue = this.getDisplayValue(this.fieldSignal().modelValue);
3340
+ this.displayValue = this.getDisplayValue(this.field().modelValue);
3075
3341
  return;
3076
3342
  }
3077
3343
  if (!this.isAddOrEdit())
3078
3344
  return;
3079
- this.fieldService.updateDependValuesMap(this.fieldKey(), value ?? '');
3345
+ this.fieldService.updateDependValuesMap(this.fieldKey(), value ?? "");
3080
3346
  // const signal = untracked(() => this.fieldService.getDependValuesMap().get(this.fieldKey()))
3081
3347
  // console.log("DependValuesMap SET", untracked(() => signal!()));
3082
3348
  // Gán data vào đầu ra của form.
@@ -3085,7 +3351,7 @@ class TreeSelectComponent extends BaseValueAccessor {
3085
3351
  }
3086
3352
  // Xử lý phụ thuộc khi change value
3087
3353
  handleValueChange(value) {
3088
- const { isParent } = this.fieldSignal();
3354
+ const { isParent } = this.field();
3089
3355
  if (isParent) {
3090
3356
  this.fieldService.updateDependValuesMap(this.fieldKey(), value);
3091
3357
  // const signal = untracked(() => this.fieldService.getDependValuesMap().get(this.fieldKey()))
@@ -3096,8 +3362,8 @@ class TreeSelectComponent extends BaseValueAccessor {
3096
3362
  this.patchFormData(this.fieldKey(), newOutputValue);
3097
3363
  }
3098
3364
  createFullApiUrl() {
3099
- const field = untracked(() => this.fieldSignal());
3100
- let fullApiUrl = field?.apiConfig?.baseUrl || '';
3365
+ const field = untracked(() => this.field());
3366
+ let fullApiUrl = field?.apiConfig?.baseUrl || "";
3101
3367
  const params = field?.apiConfig?.params || {};
3102
3368
  this.queryParams = {
3103
3369
  ...this.queryParams,
@@ -3121,8 +3387,8 @@ class TreeSelectComponent extends BaseValueAccessor {
3121
3387
  }
3122
3388
  // Step 2: Build parent-child relation
3123
3389
  for (const node of map.values()) {
3124
- if (node['parentCode']) {
3125
- const parent = map.get(node['parentCode']);
3390
+ if (node["parentCode"]) {
3391
+ const parent = map.get(node["parentCode"]);
3126
3392
  if (parent) {
3127
3393
  if (!parent.children)
3128
3394
  parent.children = [];
@@ -3141,11 +3407,15 @@ class TreeSelectComponent extends BaseValueAccessor {
3141
3407
  }
3142
3408
  // Lấy danh sách options từ GET api.
3143
3409
  getOptions() {
3144
- const field = untracked(() => this.fieldSignal());
3410
+ const field = untracked(() => this.field());
3145
3411
  const { optionsToPrepend, optionsToAppend, exceptOptions } = field;
3146
3412
  const fullApiUrl = this.createFullApiUrl();
3147
3413
  if (!fullApiUrl) {
3148
- const options = [...optionsToPrepend, ...field.options, ...optionsToAppend].filter(options => !exceptOptions.some(opts => opts[field.bindValue] === options[field.bindValue]));
3414
+ const options = [
3415
+ ...optionsToPrepend,
3416
+ ...field.options,
3417
+ ...optionsToAppend,
3418
+ ].filter((options) => !exceptOptions.some((opts) => opts[field.bindValue] === options[field.bindValue]));
3149
3419
  this.options = this.buildTree(options, field.groupBy);
3150
3420
  return;
3151
3421
  }
@@ -3157,14 +3427,18 @@ class TreeSelectComponent extends BaseValueAccessor {
3157
3427
  .subscribe({
3158
3428
  next: (res) => {
3159
3429
  this.loadingSignal.set(false);
3160
- if (!this.fieldSignal().isDisabled)
3430
+ if (!this.field().isDisabled)
3161
3431
  this.control?.enable();
3162
3432
  if (res?.code === 0) {
3163
- const options = [...optionsToPrepend, ...(res?.data ?? []), ...optionsToAppend].filter(options => !exceptOptions.some(opts => opts[field.bindValue] === options[field.bindValue]));
3433
+ const options = [
3434
+ ...optionsToPrepend,
3435
+ ...(res?.data ?? []),
3436
+ ...optionsToAppend,
3437
+ ].filter((options) => !exceptOptions.some((opts) => opts[field.bindValue] === options[field.bindValue]));
3164
3438
  this.options = this.buildTree(options, field.groupBy);
3165
3439
  // Nếu outputAs là dạng sourceObject đồng bộ sau khi load xong list.
3166
3440
  if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
3167
- const newOutputValue = this.options.find(option => option.key === this.value) ?? {
3441
+ const newOutputValue = this.options.find((option) => option.key === this.value) ?? {
3168
3442
  [field.bindValue]: this.value,
3169
3443
  };
3170
3444
  this.patchFormData(this.fieldKey(), newOutputValue);
@@ -3173,22 +3447,22 @@ class TreeSelectComponent extends BaseValueAccessor {
3173
3447
  },
3174
3448
  error: (error) => {
3175
3449
  this.loadingSignal.set(false);
3176
- if (!this.fieldSignal().isDisabled)
3450
+ if (!this.field().isDisabled)
3177
3451
  this.control?.enable();
3178
3452
  },
3179
3453
  });
3180
3454
  }
3181
3455
  handleChangeOptions(optionValue) {
3182
- const { enableFieldChange, bindValue } = untracked(() => this.fieldSignal());
3456
+ const { enableFieldChange, bindValue } = untracked(() => this.field());
3183
3457
  if (enableFieldChange) {
3184
- const option = this.options.find(option => option[bindValue] === optionValue);
3458
+ const option = this.options.find((option) => option[bindValue] === optionValue);
3185
3459
  this.fieldService.onFieldChange(this.fieldKey(), option);
3186
3460
  }
3187
3461
  this.propagateValue(optionValue);
3188
3462
  }
3189
3463
  // Xử lý output value đầu ra theo outputAs mà người dùng mong muốn.
3190
3464
  getValueByOutputAs(singleValue) {
3191
- const field = this.fieldSignal();
3465
+ const field = this.field();
3192
3466
  if (singleValue) {
3193
3467
  // Nếu outputAs là dạng single.
3194
3468
  let value = singleValue;
@@ -3200,7 +3474,7 @@ class TreeSelectComponent extends BaseValueAccessor {
3200
3474
  }
3201
3475
  // Nếu outputAs là dạng sourceObject.
3202
3476
  if (field.outputAs === SelectOutputValueTypeEnum.SourceObject) {
3203
- value = this.options.find(option => option[field.bindValue] === singleValue) ?? {
3477
+ value = this.options.find((option) => option[field.bindValue] === singleValue) ?? {
3204
3478
  [field.bindValue]: singleValue,
3205
3479
  };
3206
3480
  }
@@ -3212,14 +3486,14 @@ class TreeSelectComponent extends BaseValueAccessor {
3212
3486
  getDisplayValue(value) {
3213
3487
  if (!value)
3214
3488
  return NO_INFO_TEXT;
3215
- if (typeof value === 'object') {
3489
+ if (typeof value === "object") {
3216
3490
  return this.getCustomLabel(value);
3217
3491
  }
3218
3492
  return value;
3219
3493
  }
3220
3494
  // Lấy value ở option tag.
3221
3495
  transformOptionValue(option) {
3222
- const field = this.fieldSignal();
3496
+ const field = this.field();
3223
3497
  let optionValue;
3224
3498
  optionValue = option[field.bindValue];
3225
3499
  return optionValue;
@@ -3230,11 +3504,12 @@ class TreeSelectComponent extends BaseValueAccessor {
3230
3504
  }
3231
3505
  // Biến đổi label nếu có thuộc tính hàm biến đổi transformLabel.
3232
3506
  getCustomLabel(option) {
3233
- const field = this.fieldSignal();
3234
- if (typeof option[field.bindLabel] === 'function' || !option[field.bindLabel]) {
3235
- let warnMessage = 'Giá trị label lấy theo bindLabel hiện đang là dạng object, array hoặc các dạng còn lại của complex data';
3507
+ const field = this.field();
3508
+ if (typeof option[field.bindLabel] === "function" ||
3509
+ !option[field.bindLabel]) {
3510
+ let warnMessage = "Giá trị label lấy theo bindLabel hiện đang là dạng object, array hoặc các dạng còn lại của complex data";
3236
3511
  if (!option[field.bindLabel])
3237
- warnMessage = 'Không tồn tại giá trị label khi lấy theo bindLabel';
3512
+ warnMessage = "Không tồn tại giá trị label khi lấy theo bindLabel";
3238
3513
  // console.warn(warnMessage);
3239
3514
  return NO_INFO_TEXT;
3240
3515
  }
@@ -3253,62 +3528,55 @@ class TreeSelectComponent extends BaseValueAccessor {
3253
3528
  useExisting: forwardRef(() => TreeSelectComponent),
3254
3529
  multi: true,
3255
3530
  },
3256
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-tree-select\r\n [nzId]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n [nzNodes]=\"options\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n </nz-tree-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzTreeSelectModule }, { kind: "component", type: i2$1.NzTreeSelectComponent, selector: "nz-tree-select", inputs: ["nzId", "nzAllowClear", "nzShowExpand", "nzShowLine", "nzDropdownMatchSelectWidth", "nzCheckable", "nzHideUnMatched", "nzShowIcon", "nzShowSearch", "nzDisabled", "nzAsyncData", "nzMultiple", "nzDefaultExpandAll", "nzCheckStrictly", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualHeight", "nzExpandedIcon", "nzNotFoundContent", "nzNodes", "nzOpen", "nzSize", "nzPlaceHolder", "nzDropdownStyle", "nzDropdownClassName", "nzBackdrop", "nzStatus", "nzPlacement", "nzExpandedKeys", "nzDisplayWith", "nzMaxTagCount", "nzMaxTagPlaceholder", "nzTreeTemplate"], outputs: ["nzOpenChange", "nzCleared", "nzRemoved", "nzExpandChange", "nzTreeClick", "nzTreeCheckboxChange"], exportAs: ["nzTreeSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
3531
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-tree-select\r\n [nzId]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [title]=\"field().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"field().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n [nzNodes]=\"options\"\r\n class=\"flex-1 min-w-0\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n </nz-tree-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzTreeSelectModule }, { kind: "component", type: i2$1.NzTreeSelectComponent, selector: "nz-tree-select", inputs: ["nzId", "nzAllowClear", "nzShowExpand", "nzShowLine", "nzDropdownMatchSelectWidth", "nzCheckable", "nzHideUnMatched", "nzShowIcon", "nzShowSearch", "nzDisabled", "nzAsyncData", "nzMultiple", "nzDefaultExpandAll", "nzCheckStrictly", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualHeight", "nzExpandedIcon", "nzNotFoundContent", "nzNodes", "nzOpen", "nzSize", "nzPlaceHolder", "nzDropdownStyle", "nzDropdownClassName", "nzBackdrop", "nzStatus", "nzPlacement", "nzExpandedKeys", "nzDisplayWith", "nzMaxTagCount", "nzMaxTagPlaceholder", "nzTreeTemplate"], outputs: ["nzOpenChange", "nzCleared", "nzRemoved", "nzExpandChange", "nzTreeClick", "nzTreeCheckboxChange"], exportAs: ["nzTreeSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
3257
3532
  }
3258
3533
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: TreeSelectComponent, decorators: [{
3259
3534
  type: Component,
3260
- args: [{ selector: 'app-tree-select', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$7, ...COMPONENTS$3], providers: [
3535
+ args: [{ selector: "app-tree-select", standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$4, ...COMPONENTS$1], providers: [
3261
3536
  {
3262
3537
  provide: NG_VALUE_ACCESSOR,
3263
3538
  useExisting: forwardRef(() => TreeSelectComponent),
3264
3539
  multi: true,
3265
3540
  },
3266
- ], template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-tree-select\r\n [nzId]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n [nzNodes]=\"options\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n </nz-tree-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"] }]
3541
+ ], template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-tree-select\r\n [nzId]=\"fieldKey()\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"displayPlaceholder()\"\r\n [title]=\"field().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"field().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n [nzNodes]=\"options\"\r\n class=\"flex-1 min-w-0\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n </nz-tree-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n" }]
3267
3542
  }], ctorParameters: () => [], propDecorators: { isCustomInputText: [{
3268
3543
  type: Input
3269
3544
  }] } });
3270
3545
 
3271
- const ZORRO_MODULES$6 = [NzInputModule];
3546
+ const ZORRO_MODULES$3 = [NzInputModule];
3272
3547
  class TextareaComponent extends BaseValueAccessor {
3273
3548
  constructor() {
3274
3549
  super();
3275
- this.fieldSignal = computed(() => {
3550
+ this.field = computed(() => {
3276
3551
  const base = this.fieldInput();
3277
- if (!(base instanceof TextareaField) || base.type !== FieldTypeEnum.Textarea) {
3278
- throw new Error('Expected TextareaField');
3552
+ if (!(base instanceof TextareaField) ||
3553
+ base.type !== FieldTypeEnum.Textarea) {
3554
+ throw new Error("Expected TextareaField");
3279
3555
  }
3280
3556
  return base;
3281
3557
  });
3282
- this.textareaDisplayValue = '';
3283
3558
  this.displayNoValueText = NO_INFO_TEXT;
3284
- // 1. Effect chung Add + Edit
3285
- effect(() => {
3286
- if (this.isAddOrEdit()) {
3287
- const field = this.fieldSignal();
3288
- const { placeholder } = field;
3289
- if (!placeholder)
3290
- field.placeholder = createPlaceholder(field);
3291
- }
3292
- });
3293
- // Trigger reset từ bên ngoài form.
3294
- effect(() => {
3295
- const reset = this.dFormService.resetTrigger();
3296
- if (reset?.formId === this.formId) {
3297
- this.reset();
3298
- }
3559
+ this.viewDisplayValue = computed(() => {
3560
+ // 1. Nếu không phải View thì không cần quan tâm text này
3561
+ if (!this.isView())
3562
+ return "";
3563
+ // 2. Lấy giá trị từ valueSignal
3564
+ const val = this.valueSignal();
3565
+ return val ? val : NO_INFO_TEXT;
3299
3566
  });
3300
3567
  }
3568
+ // Xử lý tạo placeholder text tự động nếu chưa truyền placeholder vào config của field
3569
+ resolvePlaceholderText() {
3570
+ return createInputPlaceholder(this.field());
3571
+ }
3301
3572
  handleChangeTextarea(event) {
3302
3573
  const textareaValue = event.target.value;
3303
3574
  this.propagateValue(textareaValue);
3304
3575
  // Gán data vào đầu ra của form.
3305
- this.patchFormData(this.fieldKey(), textareaValue && textareaValue.replace(/\n+/g, 'br_enter').replace(/br_enter+/g, '\n'));
3576
+ this.patchFormData(this.fieldKey(), textareaValue &&
3577
+ textareaValue.replace(/\n+/g, "br_enter").replace(/br_enter+/g, "\n"));
3306
3578
  }
3307
3579
  writeValueToView(value) {
3308
- if (this.isView()) {
3309
- this.textareaDisplayValue = this.fieldSignal().modelValue || NO_INFO_TEXT;
3310
- return;
3311
- }
3312
3580
  if (!this.isAddOrEdit())
3313
3581
  return;
3314
3582
  // Gán data vào đầu ra của form.
@@ -3321,17 +3589,17 @@ class TextareaComponent extends BaseValueAccessor {
3321
3589
  useExisting: forwardRef(() => TextareaComponent),
3322
3590
  multi: true,
3323
3591
  },
3324
- ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (textareaDisplayValue) {\r\n <p [innerHTML]=\"textareaDisplayValue\" class=\"disabled-textarea break-work mb0\"></p>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" class=\"modeView\" />\r\n }\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (textareaDisplayValue) {\r\n <p [innerHTML]=\"textareaDisplayValue\" class=\"mode-view mb-0 break-work mb0\"></p>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n} @else {\r\n <textarea\r\n nz-input\r\n [id]=\"fieldSignal().key\"\r\n [nzAutosize]=\"{ minRows: 1, maxRows: 6 }\"\r\n [value]=\"value\"\r\n [disabled]=\"isDisabled\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n (input)=\"handleChangeTextarea($event)\"\r\n >\r\n </textarea>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.disabled-textarea{padding:4px 8px;border:1px solid #cbd5e1;border-radius:4px;color:#575757!important;cursor:not-allowed;pointer-events:none;font-size:12px}.mode-view{pointer-events:none;color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "directive", type: i1$1.NzAutosizeDirective, selector: "textarea[nzAutosize]", inputs: ["nzAutosize"], exportAs: ["nzAutosize"] }] }); }
3592
+ ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n @if (viewDisplayValue(); as displayValue) {\r\n <p\r\n [innerHTML]=\"displayValue\"\r\n class=\"disabled-textarea break-work mb0\"\r\n ></p>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" class=\"modeView\" />\r\n }\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n @if (viewDisplayValue(); as displayValue) {\r\n <p [innerHTML]=\"displayValue\" class=\"mode-view mb-0 break-work mb0\"></p>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n} @else {\r\n <textarea\r\n nz-input\r\n [nzAutosize]=\"{ minRows: 1, maxRows: 8 }\"\r\n [value]=\"value\"\r\n [disabled]=\"isDisabled\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n (input)=\"handleChangeTextarea($event)\"\r\n >\r\n </textarea>\r\n}\r\n", styles: [".disabled-textarea{padding:4px 8px;border:1px solid #cbd5e1;color:#575757!important;cursor:not-allowed;pointer-events:none}.mode-view{pointer-events:none;color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "directive", type: i1$1.NzAutosizeDirective, selector: "textarea[nzAutosize]", inputs: ["nzAutosize"], exportAs: ["nzAutosize"] }] }); }
3325
3593
  }
3326
3594
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: TextareaComponent, decorators: [{
3327
3595
  type: Component,
3328
- args: [{ selector: 'app-textarea', standalone: true, imports: [...ZORRO_MODULES$6], providers: [
3596
+ args: [{ selector: "app-textarea", standalone: true, imports: [...ZORRO_MODULES$3], providers: [
3329
3597
  {
3330
3598
  provide: NG_VALUE_ACCESSOR,
3331
3599
  useExisting: forwardRef(() => TextareaComponent),
3332
3600
  multi: true,
3333
3601
  },
3334
- ], template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (textareaDisplayValue) {\r\n <p [innerHTML]=\"textareaDisplayValue\" class=\"disabled-textarea break-work mb0\"></p>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" class=\"modeView\" />\r\n }\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (textareaDisplayValue) {\r\n <p [innerHTML]=\"textareaDisplayValue\" class=\"mode-view mb-0 break-work mb0\"></p>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n} @else {\r\n <textarea\r\n nz-input\r\n [id]=\"fieldSignal().key\"\r\n [nzAutosize]=\"{ minRows: 1, maxRows: 6 }\"\r\n [value]=\"value\"\r\n [disabled]=\"isDisabled\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n (input)=\"handleChangeTextarea($event)\"\r\n >\r\n </textarea>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.disabled-textarea{padding:4px 8px;border:1px solid #cbd5e1;border-radius:4px;color:#575757!important;cursor:not-allowed;pointer-events:none;font-size:12px}.mode-view{pointer-events:none;color:#575757;font-size:12px}\n"] }]
3602
+ ], template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n @if (viewDisplayValue(); as displayValue) {\r\n <p\r\n [innerHTML]=\"displayValue\"\r\n class=\"disabled-textarea break-work mb0\"\r\n ></p>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" class=\"modeView\" />\r\n }\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n @if (viewDisplayValue(); as displayValue) {\r\n <p [innerHTML]=\"displayValue\" class=\"mode-view mb-0 break-work mb0\"></p>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n} @else {\r\n <textarea\r\n nz-input\r\n [nzAutosize]=\"{ minRows: 1, maxRows: 8 }\"\r\n [value]=\"value\"\r\n [disabled]=\"isDisabled\"\r\n [placeholder]=\"displayPlaceholder()\"\r\n (input)=\"handleChangeTextarea($event)\"\r\n >\r\n </textarea>\r\n}\r\n", styles: [".disabled-textarea{padding:4px 8px;border:1px solid #cbd5e1;color:#575757!important;cursor:not-allowed;pointer-events:none}.mode-view{pointer-events:none;color:#575757;font-size:12px}\n"] }]
3335
3603
  }], ctorParameters: () => [] });
3336
3604
 
3337
3605
  class FileService {
@@ -3372,31 +3640,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
3372
3640
  }]
3373
3641
  }] });
3374
3642
 
3375
- const ZORRO_MODULES$5 = [NzUploadModule];
3643
+ const modules$2 = [CommonModule];
3644
+ const zorroModules$2 = [NzUploadModule, NzIconModule];
3376
3645
  class AvatarUploadComponent extends BaseValueAccessor {
3377
3646
  constructor() {
3378
3647
  super();
3379
3648
  this.size = 100; // kích thước avatar
3380
- this.fieldSignal = computed(() => {
3649
+ this.field = computed(() => {
3381
3650
  const base = this.fieldInput();
3382
3651
  if (!(base instanceof AvatarUploadField) ||
3383
- (base.type !== FieldTypeEnum.AvatarUpload && base.controlType !== ControlTypeEnum.Upload)) {
3384
- throw new Error('Expected AvatarUploadField');
3652
+ (base.type !== FieldTypeEnum.AvatarUpload &&
3653
+ base.controlType !== ControlTypeEnum.Upload)) {
3654
+ throw new Error("Expected AvatarUploadField");
3385
3655
  }
3386
3656
  return base;
3387
3657
  });
3388
- this.avatarUrl = null;
3389
- // Mảng lưu trữ objectStorage liên quan đến tập tin.
3390
- this.objStorageList = signal([]);
3658
+ // Inject services
3391
3659
  this.fileService = inject(FileService);
3392
3660
  this.formConfigService = inject(FormConfigService);
3393
3661
  this.nzNotification = inject(NzNotificationService);
3662
+ this.avatarUrl = signal(null);
3663
+ this.uploading = signal(false);
3664
+ // Mảng lưu trữ objectStorage liên quan đến tập tin.
3665
+ this.objStorageList = signal([]);
3394
3666
  this.beforeUpload = (file, fileList) => {
3395
3667
  // const originFile = file?.originFileObj as unknown as File; // có thể ở version mới nhất đã fix lỗi NzUploadFile nhưng trả về File của tham số file.
3396
3668
  // Xử lý gán vào CVA để tiến hành validation theo các props đã truyền vào.
3397
3669
  const originFile = file;
3398
3670
  this.propagateValue(originFile);
3399
- const { maxSizeInMB, acceptedMimeTypes } = this.fieldSignal();
3671
+ const { maxSizeInMB, acceptedMimeTypes } = this.field();
3400
3672
  const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
3401
3673
  const fileType = file.type;
3402
3674
  if (fileSizeInMB > maxSizeInMB || !acceptedMimeTypes.includes(fileType)) {
@@ -3414,13 +3686,6 @@ class AvatarUploadComponent extends BaseValueAccessor {
3414
3686
  // Validation đã thỏa, tiến hành upload file.
3415
3687
  return this.uploadFile(originFile);
3416
3688
  };
3417
- // Trigger reset từ bên ngoài form.
3418
- effect(() => {
3419
- const reset = this.dFormService.resetTrigger();
3420
- if (reset?.formId === this.formId) {
3421
- this.reset();
3422
- }
3423
- });
3424
3689
  }
3425
3690
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
3426
3691
  writeValueToView(value) {
@@ -3435,31 +3700,33 @@ class AvatarUploadComponent extends BaseValueAccessor {
3435
3700
  handleInitAllData(value) {
3436
3701
  // Reset khi đổi mode, hoặc model, hoặc fields.
3437
3702
  this.objStorageList.set([]);
3438
- this.avatarUrl = '';
3703
+ this.avatarUrl.set(null);
3439
3704
  if (isValidJSON(value)) {
3440
3705
  const objStorageList = JSON.parse(value);
3441
3706
  this.objStorageList.set(objStorageList);
3442
- this.avatarUrl = this.formConfigService.getViewFileUrl(objStorageList[0].name ?? '', false);
3707
+ this.avatarUrl.set(this.formConfigService.getViewFileUrl(objStorageList[0]?.name ?? "", false));
3443
3708
  }
3444
3709
  }
3445
3710
  uploadFile(file) {
3446
- const { uploadTemp } = this.fieldSignal();
3447
- const newFileName = this.formConfigService.generateRandomFileName(file.name, 'avatar');
3711
+ this.uploading.set(true);
3712
+ const { uploadTemp } = this.field();
3713
+ const newFileName = this.formConfigService.generateRandomFileName(file.name, "avatar");
3448
3714
  const renamedFile = new File([file], newFileName, { type: file.type });
3449
3715
  const formData = new FormData();
3450
- formData.append('File', renamedFile);
3451
- // formData.append('isTemp', 'true');
3716
+ formData.append("File", renamedFile);
3452
3717
  if (uploadTemp)
3453
- formData.append('isTemp', 'true');
3718
+ formData.append("isTemp", "true");
3454
3719
  return this.fileService
3455
- .uploadFile(renamedFile, formData, { uploadUrl: this.fieldSignal().apiFileConfig.uploadUrl })
3456
- .pipe(this.takeUntilDestroy())
3720
+ .uploadFile(renamedFile, formData, {
3721
+ uploadUrl: this.field().apiFileConfig.uploadUrl,
3722
+ })
3723
+ .pipe(this.takeUntilDestroy(), finalize(() => this.uploading.set(false)))
3457
3724
  .subscribe({
3458
- next: res => {
3725
+ next: (res) => {
3459
3726
  const { code, data, message } = res;
3460
3727
  if (code === 0) {
3461
3728
  this.objStorageList.set([data]);
3462
- this.avatarUrl = this.formConfigService.getViewFileUrl(data.name ?? '', true);
3729
+ this.avatarUrl.set(this.formConfigService.getViewFileUrl(data?.name ?? "", true));
3463
3730
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
3464
3731
  }
3465
3732
  else {
@@ -3467,21 +3734,18 @@ class AvatarUploadComponent extends BaseValueAccessor {
3467
3734
  }
3468
3735
  },
3469
3736
  error: () => {
3470
- console.error('Lỗi trong quá trình tải lên ảnh đại diện!!');
3737
+ console.error(UPLOAD_FILE_ERROR);
3471
3738
  },
3472
3739
  });
3473
3740
  }
3474
- handleDelete(file) {
3475
- this.objStorageList.set([]);
3476
- this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
3477
- }
3478
- handlePreview() { }
3479
3741
  deleteObjStorage(id) {
3480
3742
  return this.fileService
3481
- .deleteObjStorage(id, { deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl })
3743
+ .deleteObjStorage(id, {
3744
+ deleteUrl: this.field().apiFileConfig.deleteUrl,
3745
+ })
3482
3746
  .pipe(this.takeUntilDestroy())
3483
3747
  .subscribe({
3484
- next: res => {
3748
+ next: (res) => {
3485
3749
  const { code, message } = res;
3486
3750
  if (code === 0) {
3487
3751
  this.objStorageList.set([]);
@@ -3492,7 +3756,7 @@ class AvatarUploadComponent extends BaseValueAccessor {
3492
3756
  }
3493
3757
  },
3494
3758
  error: (err) => {
3495
- console.error('Lỗi trong quá trình xóa tập tin khỏi máy chủ!!');
3759
+ console.error(DELETE_FILE_ERROR);
3496
3760
  // Tạm do api xóa có vấn đề
3497
3761
  this.objStorageList.set([]);
3498
3762
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
@@ -3506,27 +3770,27 @@ class AvatarUploadComponent extends BaseValueAccessor {
3506
3770
  useExisting: forwardRef(() => AvatarUploadComponent),
3507
3771
  multi: true,
3508
3772
  },
3509
- ], usesInheritance: true, ngImport: i0, template: "<nz-upload\r\n [nzDisabled]=\"isDisabled\"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n [nzDisabled]=\"!!isView()\"\r\n>\r\n <ng-container *ngTemplateOutlet=\"renderAvatarTpl\"></ng-container>\r\n</nz-upload>\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB avatar -->\r\n<ng-template #renderAvatarTpl>\r\n <div class=\"avatar-wrapper\" [ngStyle]=\"{ width: size + 'px', height: size + 'px' }\">\r\n @if (!avatarUrl && isAddOrEdit()) {\r\n <span nz-icon nzType=\"plus\" nzTheme=\"outline\" class=\"upload-icon\"></span>\r\n }\r\n\r\n @if (avatarUrl) {\r\n <img [src]=\"avatarUrl\" alt=\"\" class=\"avatar-img\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".avatar-wrapper{display:flex;align-items:center;justify-content:center;border:1px dashed #d9d9d9;border-radius:8px;background-color:#fafafa;cursor:pointer;overflow:hidden}.avatar-wrapper .upload-icon{font-size:24px;color:#999}.avatar-wrapper .avatar-img{width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }] }); }
3773
+ ], usesInheritance: true, ngImport: i0, template: "<nz-upload\r\n [nzDisabled]=\"disabledSignal() || isView()\"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n>\r\n <ng-container *ngTemplateOutlet=\"renderAvatarTpl\"></ng-container>\r\n</nz-upload>\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB avatar -->\r\n<ng-template #renderAvatarTpl>\r\n <div\r\n class=\"avatar-wrapper\"\r\n [ngStyle]=\"{ width: size + 'px', height: size + 'px' }\"\r\n >\r\n @if (uploading()) {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"upload-icon\"\r\n ></span>\r\n } @else if (!avatarUrl() && isAddOrEdit()) {\r\n <span nz-icon nzType=\"plus\" nzTheme=\"outline\" class=\"upload-icon\"></span>\r\n } @else if (avatarUrl()) {\r\n <img [src]=\"avatarUrl()\" alt=\"\" class=\"avatar-img\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".avatar-wrapper{display:flex;align-items:center;justify-content:center;border:1px dashed #d9d9d9;border-radius:8px;background-color:#fafafa;cursor:pointer;overflow:hidden}.avatar-wrapper .upload-icon{font-size:24px;color:#999}.avatar-wrapper .avatar-img{width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }] }); }
3510
3774
  }
3511
3775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: AvatarUploadComponent, decorators: [{
3512
3776
  type: Component,
3513
- args: [{ selector: 'app-avatar-upload', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$5], providers: [
3777
+ args: [{ selector: "app-avatar-upload", standalone: true, imports: [...modules$2, ...zorroModules$2], providers: [
3514
3778
  {
3515
3779
  provide: NG_VALUE_ACCESSOR,
3516
3780
  useExisting: forwardRef(() => AvatarUploadComponent),
3517
3781
  multi: true,
3518
3782
  },
3519
- ], template: "<nz-upload\r\n [nzDisabled]=\"isDisabled\"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n [nzDisabled]=\"!!isView()\"\r\n>\r\n <ng-container *ngTemplateOutlet=\"renderAvatarTpl\"></ng-container>\r\n</nz-upload>\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB avatar -->\r\n<ng-template #renderAvatarTpl>\r\n <div class=\"avatar-wrapper\" [ngStyle]=\"{ width: size + 'px', height: size + 'px' }\">\r\n @if (!avatarUrl && isAddOrEdit()) {\r\n <span nz-icon nzType=\"plus\" nzTheme=\"outline\" class=\"upload-icon\"></span>\r\n }\r\n\r\n @if (avatarUrl) {\r\n <img [src]=\"avatarUrl\" alt=\"\" class=\"avatar-img\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".avatar-wrapper{display:flex;align-items:center;justify-content:center;border:1px dashed #d9d9d9;border-radius:8px;background-color:#fafafa;cursor:pointer;overflow:hidden}.avatar-wrapper .upload-icon{font-size:24px;color:#999}.avatar-wrapper .avatar-img{width:100%;height:100%;object-fit:cover}\n"] }]
3783
+ ], template: "<nz-upload\r\n [nzDisabled]=\"disabledSignal() || isView()\"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n>\r\n <ng-container *ngTemplateOutlet=\"renderAvatarTpl\"></ng-container>\r\n</nz-upload>\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB avatar -->\r\n<ng-template #renderAvatarTpl>\r\n <div\r\n class=\"avatar-wrapper\"\r\n [ngStyle]=\"{ width: size + 'px', height: size + 'px' }\"\r\n >\r\n @if (uploading()) {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"upload-icon\"\r\n ></span>\r\n } @else if (!avatarUrl() && isAddOrEdit()) {\r\n <span nz-icon nzType=\"plus\" nzTheme=\"outline\" class=\"upload-icon\"></span>\r\n } @else if (avatarUrl()) {\r\n <img [src]=\"avatarUrl()\" alt=\"\" class=\"avatar-img\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".avatar-wrapper{display:flex;align-items:center;justify-content:center;border:1px dashed #d9d9d9;border-radius:8px;background-color:#fafafa;cursor:pointer;overflow:hidden}.avatar-wrapper .upload-icon{font-size:24px;color:#999}.avatar-wrapper .avatar-img{width:100%;height:100%;object-fit:cover}\n"] }]
3520
3784
  }], ctorParameters: () => [], propDecorators: { size: [{
3521
3785
  type: Input
3522
3786
  }] } });
3523
3787
 
3524
- const ZORRO_MODULES$4 = [NzUploadModule, NzIconModule, NzButtonModule];
3788
+ const ZORRO_MODULES$2 = [NzUploadModule, NzIconModule, NzButtonModule];
3525
3789
  class MultiFileUploadComponent extends BaseValueAccessor {
3526
3790
  constructor() {
3527
3791
  super();
3528
3792
  // Cast lại đúng instance là MultiFileUploadFile.
3529
- this.fieldSignal = computed(() => {
3793
+ this.field = computed(() => {
3530
3794
  const base = this.fieldInput();
3531
3795
  if (!(base instanceof MultiFileUploadField) ||
3532
3796
  (base.type !== FieldTypeEnum.MultiFileUpload &&
@@ -3540,7 +3804,12 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3540
3804
  // Mảng đại diện các images.
3541
3805
  this.allFiles = signal([]);
3542
3806
  // Text hướng dẫn các loại extension được upload.
3543
- this.fileExtensionGuide = signal("");
3807
+ this.fileExtensionGuide = computed(() => {
3808
+ if (!this.isAddOrEdit())
3809
+ return "";
3810
+ const { acceptedMimeTypes } = this.field();
3811
+ return `Hỗ trợ định dạng ${getExtensions(acceptedMimeTypes).join(", ")}`;
3812
+ });
3544
3813
  // Đếm số file đã pass beforeUpload nhưng chưa upload xong (đang trong hàng chờ customRequest/uploadFile).
3545
3814
  // Dùng để tính toán count chính xác khi user chọn nhiều file cùng lúc.
3546
3815
  this.pendingCount = signal(0);
@@ -3549,8 +3818,6 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3549
3818
  // Timer dùng để phát hiện kết thúc một batch (setTimeout(0)).
3550
3819
  this._batchTimer = null;
3551
3820
  this.fileService = inject(FileService);
3552
- this.toast = inject(ToastrService);
3553
- this.translate = inject(TranslateService);
3554
3821
  this.nzMessage = inject(NzMessageService);
3555
3822
  this.nzNotification = inject(NzNotificationService);
3556
3823
  this.formConfigService = inject(FormConfigService);
@@ -3593,22 +3860,6 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3593
3860
  // Validation đã thỏa, tiến hành upload file.
3594
3861
  return this.uploadFile(originFile, uid);
3595
3862
  };
3596
- // 1. Effect chung Add + Edit
3597
- effect(() => {
3598
- if (this.isAddOrEdit()) {
3599
- const field = untracked(() => this.fieldSignal());
3600
- const { acceptedMimeTypes } = field;
3601
- this.fileExtensionGuide.set(`Hỗ trợ các định dạng ${getExtensions(acceptedMimeTypes).join(", ")}`);
3602
- }
3603
- });
3604
- // Trigger reset từ bên ngoài form.
3605
- effect(() => {
3606
- const reset = this.dFormService.resetTrigger();
3607
- if (reset?.formId === this.formId) {
3608
- this._clearBatch(); // Hủy batch đang xử lý trước khi reset.
3609
- this.reset();
3610
- }
3611
- });
3612
3863
  }
3613
3864
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
3614
3865
  writeValueToView(value) {
@@ -3652,16 +3903,11 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3652
3903
  const batch = [...this._batchBuffer];
3653
3904
  this._batchBuffer = [];
3654
3905
  this._batchTimer = null;
3655
- const { maxFileCount, maxSizeInMB, acceptedMimeTypes } = this.fieldSignal();
3906
+ const { maxFileCount, maxSizeInMB, acceptedMimeTypes } = this.field();
3656
3907
  // Số file hiện tại: đã upload xong + đang trong hàng chờ upload.
3657
3908
  const currentCount = this.allFiles().length + this.pendingCount();
3658
3909
  // ── CHECK 1: Tổng batch vượt maxFileCount → reject TOÀN BỘ batch ──
3659
3910
  if (currentCount + batch.length > maxFileCount) {
3660
- // this.toast.warning(
3661
- // this.translate.instant(
3662
- // `Không thể tải lên: số lượng file chọn (${batch.length}) vượt quá giới hạn cho phép. Tối đa ${maxFileCount} tập tin.`,
3663
- // ),
3664
- // );
3665
3911
  this.nzNotification.warning("Cảnh báo", `Không thể tải lên: số lượng file tải lên vượt quá giới hạn cho phép. Tối đa ${maxFileCount} tập tin.`, { nzDuration: 4500 });
3666
3912
  batch.forEach(({ resolve }) => resolve(false));
3667
3913
  return;
@@ -3706,7 +3952,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3706
3952
  }
3707
3953
  }
3708
3954
  uploadFile(file, uid) {
3709
- const { apiFileConfig, uploadTemp } = this.fieldSignal();
3955
+ const { apiFileConfig, uploadTemp } = this.field();
3710
3956
  const newFileName = this.formConfigService.generateRandomFileName(file.name, "file");
3711
3957
  const renamedFile = new File([file], newFileName, { type: file.type });
3712
3958
  const mimeCfg = apiFileConfig.perMimeTypeConfig?.[file.type]; // Lấy mimeTypes file upload.
@@ -3771,7 +4017,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3771
4017
  }
3772
4018
  handleDelete(file) {
3773
4019
  const { objStorage, uploadStatus } = file;
3774
- const { uploadTemp } = this.fieldSignal();
4020
+ const { uploadTemp } = this.field();
3775
4021
  // Loại bỏ và cập nhật danh sách ObjectStorage.
3776
4022
  const newObjStorageList = this.objStorageList().filter((item) => item.id !== objStorage?.id);
3777
4023
  this.objStorageList.set(newObjStorageList);
@@ -3797,7 +4043,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3797
4043
  deleteObjStorage(id) {
3798
4044
  return this.fileService
3799
4045
  .deleteObjStorage(id, {
3800
- deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl,
4046
+ deleteUrl: this.field().apiFileConfig.deleteUrl,
3801
4047
  })
3802
4048
  .pipe(this.takeUntilDestroy())
3803
4049
  .subscribe({
@@ -3878,29 +4124,35 @@ class MultiFileUploadComponent extends BaseValueAccessor {
3878
4124
  useExisting: forwardRef(() => MultiFileUploadComponent),
3879
4125
  multi: true,
3880
4126
  },
3881
- ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }] }); }
4127
+ ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ field().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (field().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n} @else {\r\n @if (isView()) {\r\n <span class=\"no-text-info\">Kh\u00F4ng c\u00F3 th\u00F4ng tin</span>\r\n }\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.no-text-info{pointer-events:none;background-color:#fff;color:#575757!important}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }] }); }
3882
4128
  }
3883
4129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiFileUploadComponent, decorators: [{
3884
4130
  type: Component,
3885
- args: [{ selector: "app-multi-file-upload", standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$4], providers: [
4131
+ args: [{ selector: "app-multi-file-upload", standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$2], providers: [
3886
4132
  {
3887
4133
  provide: NG_VALUE_ACCESSOR,
3888
4134
  useExisting: forwardRef(() => MultiFileUploadComponent),
3889
4135
  multi: true,
3890
4136
  },
3891
- ], template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"] }]
4137
+ ], template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ field().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (field().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n} @else {\r\n @if (isView()) {\r\n <span class=\"no-text-info\">Kh\u00F4ng c\u00F3 th\u00F4ng tin</span>\r\n }\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.no-text-info{pointer-events:none;background-color:#fff;color:#575757!important}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"] }]
3892
4138
  }], ctorParameters: () => [] });
3893
4139
 
3894
- const ZORRO_MODULES$3 = [NzUploadModule, NzIconModule, NzButtonModule, NzImageModule];
4140
+ const ZORRO_MODULES$1 = [
4141
+ NzUploadModule,
4142
+ NzIconModule,
4143
+ NzButtonModule,
4144
+ NzImageModule,
4145
+ ];
3895
4146
  class MultiImageUploadComponent extends BaseValueAccessor {
3896
4147
  constructor() {
3897
4148
  super();
3898
4149
  // Cast lại đúng instance là MultiImageUploadField.
3899
- this.fieldSignal = computed(() => {
4150
+ this.field = computed(() => {
3900
4151
  const base = this.fieldInput();
3901
4152
  if (!(base instanceof MultiImageUploadField) ||
3902
- (base.type !== FieldTypeEnum.MultiImageUpload && base.controlType !== ControlTypeEnum.Upload)) {
3903
- throw new Error('Expected MultiImageUploadField');
4153
+ (base.type !== FieldTypeEnum.MultiImageUpload &&
4154
+ base.controlType !== ControlTypeEnum.Upload)) {
4155
+ throw new Error("Expected MultiImageUploadField");
3904
4156
  }
3905
4157
  return base;
3906
4158
  });
@@ -3908,9 +4160,8 @@ class MultiImageUploadComponent extends BaseValueAccessor {
3908
4160
  this.objStorageList = signal([]);
3909
4161
  // Mảng đại diện các images.
3910
4162
  this.allFiles = signal([]);
4163
+ this.uploading = signal(false);
3911
4164
  this.fileService = inject(FileService);
3912
- this.toast = inject(ToastrService);
3913
- this.translate = inject(TranslateService);
3914
4165
  this.nzNotification = inject(NzNotificationService);
3915
4166
  this.formConfigService = inject(FormConfigService);
3916
4167
  // Xử lý trước khi upload.
@@ -3919,13 +4170,10 @@ class MultiImageUploadComponent extends BaseValueAccessor {
3919
4170
  // Xử lý gán vào CVA để tiến hành validation theo các props đã truyền vào.
3920
4171
  const originFile = file;
3921
4172
  this.propagateValue(originFile);
3922
- const { maxFileCount, maxSizeInMB, acceptedMimeTypes } = this.fieldSignal();
4173
+ const { maxFileCount, maxSizeInMB, acceptedMimeTypes } = this.field();
3923
4174
  // Kiểm tra số lượng tối đa hình ảnh có thể tải lên.
3924
4175
  if (this.allFiles().length + 1 > maxFileCount) {
3925
- // this.toast.warning(
3926
- // this.translate.instant(`Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`),
3927
- // );
3928
- this.nzNotification.warning('Cảnh báo', `Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`, { nzDuration: 4500 });
4176
+ this.nzNotification.warning("Cảnh báo", `Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`, { nzDuration: 4500 });
3929
4177
  return false;
3930
4178
  }
3931
4179
  const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
@@ -3946,13 +4194,6 @@ class MultiImageUploadComponent extends BaseValueAccessor {
3946
4194
  // Validation đã thỏa, tiến hành upload file.
3947
4195
  return this.uploadFile(originFile);
3948
4196
  };
3949
- // Trigger reset từ bên ngoài form.
3950
- effect(() => {
3951
- const reset = this.dFormService.resetTrigger();
3952
- if (reset?.formId === this.formId) {
3953
- this.reset();
3954
- }
3955
- });
3956
4197
  }
3957
4198
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
3958
4199
  writeValueToView(value) {
@@ -3973,17 +4214,18 @@ class MultiImageUploadComponent extends BaseValueAccessor {
3973
4214
  const objStorageList = JSON.parse(value);
3974
4215
  this.objStorageList.set(objStorageList);
3975
4216
  // Khởi tạo danh sách MyUploadFile từ data json.
3976
- const myUploadFiles = objStorageList.map(objStorage => ({
4217
+ const myUploadFiles = objStorageList.map((objStorage) => ({
3977
4218
  objStorage,
3978
4219
  isTemp: false,
3979
- previewUrl: this.formConfigService.getViewFileUrl(objStorage.name ?? '', false),
4220
+ previewUrl: this.formConfigService.getViewFileUrl(objStorage.name ?? "", false),
3980
4221
  }));
3981
4222
  this.allFiles.set(myUploadFiles);
3982
4223
  }
3983
4224
  }
3984
4225
  uploadFile(file) {
3985
- const { apiFileConfig, uploadTemp } = this.fieldSignal();
3986
- const newFileName = this.formConfigService.generateRandomFileName(file.name, 'file');
4226
+ this.uploading.set(true);
4227
+ const { apiFileConfig, uploadTemp } = this.field();
4228
+ const newFileName = this.formConfigService.generateRandomFileName(file.name, "file");
3987
4229
  const renamedFile = new File([file], newFileName, { type: file.type });
3988
4230
  const mimeCfg = apiFileConfig.perMimeTypeConfig?.[file.type]; // Lấy mimeTypes file upload.
3989
4231
  const uploadUrl = mimeCfg?.uploadUrl ?? apiFileConfig.uploadUrl; // Nếu có config upload riêng cho mimeTypes đang kiểm tra.
@@ -3992,26 +4234,26 @@ class MultiImageUploadComponent extends BaseValueAccessor {
3992
4234
  : (() => {
3993
4235
  // Mặc định
3994
4236
  const fd = new FormData();
3995
- fd.append('File', renamedFile);
4237
+ fd.append("File", renamedFile);
3996
4238
  // fd.append('isTemp', 'true');
3997
4239
  return fd;
3998
4240
  })();
3999
4241
  if (uploadTemp)
4000
- formData.append('isTemp', 'true');
4242
+ formData.append("isTemp", "true");
4001
4243
  return this.fileService
4002
4244
  .uploadFile(renamedFile, formData, { uploadUrl })
4003
- .pipe(this.takeUntilDestroy())
4245
+ .pipe(this.takeUntilDestroy(), finalize(() => this.uploading.set(false)))
4004
4246
  .subscribe({
4005
- next: res => {
4247
+ next: (res) => {
4006
4248
  const { code, data, message } = res;
4007
4249
  if (code === 0) {
4008
- this.objStorageList.update(curr => [...curr, data]);
4009
- this.allFiles.update(curr => [
4250
+ this.objStorageList.update((curr) => [...curr, data]);
4251
+ this.allFiles.update((curr) => [
4010
4252
  ...curr,
4011
4253
  {
4012
4254
  objStorage: data,
4013
4255
  isTemp: true,
4014
- previewUrl: this.formConfigService.getViewFileUrl(data.name ?? '', true),
4256
+ previewUrl: this.formConfigService.getViewFileUrl(data.name ?? "", true),
4015
4257
  },
4016
4258
  ]);
4017
4259
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
@@ -4021,30 +4263,32 @@ class MultiImageUploadComponent extends BaseValueAccessor {
4021
4263
  }
4022
4264
  },
4023
4265
  error: (err) => {
4024
- console.error('Lỗi trong quá trình tải hình ảnh đến máy chủ!!');
4266
+ console.error("Lỗi trong quá trình tải hình ảnh đến máy chủ!!");
4025
4267
  },
4026
4268
  });
4027
4269
  }
4028
4270
  handleDelete(file) {
4029
4271
  const { objStorage } = file;
4030
4272
  // Loại bỏ và cập nhật danh sách ObjectStorage.
4031
- const newObjStorageList = this.objStorageList().filter(item => item.id !== objStorage?.id);
4273
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== objStorage?.id);
4032
4274
  this.objStorageList.set(newObjStorageList);
4033
4275
  // Loại bỏ và cập nhật danh sách MyUploadFile.
4034
- const newAllFiles = this.allFiles().filter(item => item.objStorage?.id !== objStorage?.id);
4276
+ const newAllFiles = this.allFiles().filter((item) => item.objStorage?.id !== objStorage?.id);
4035
4277
  this.allFiles.set(newAllFiles);
4036
4278
  // Cập nhật đầu ra cho form.
4037
4279
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
4038
4280
  }
4039
4281
  deleteObjStorage(id) {
4040
4282
  return this.fileService
4041
- .deleteObjStorage(id, { deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl })
4283
+ .deleteObjStorage(id, {
4284
+ deleteUrl: this.field().apiFileConfig.deleteUrl,
4285
+ })
4042
4286
  .pipe(this.takeUntilDestroy())
4043
4287
  .subscribe({
4044
- next: res => {
4288
+ next: (res) => {
4045
4289
  const { code, message } = res;
4046
4290
  if (code === 0) {
4047
- const newObjStorageList = this.objStorageList().filter(item => item.id !== id);
4291
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== id);
4048
4292
  this.objStorageList.set(newObjStorageList);
4049
4293
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
4050
4294
  }
@@ -4053,9 +4297,9 @@ class MultiImageUploadComponent extends BaseValueAccessor {
4053
4297
  }
4054
4298
  },
4055
4299
  error: (err) => {
4056
- console.error('Lỗi trong quá trình xóa tập tin khỏi máy chủ!!');
4300
+ console.error("Lỗi trong quá trình xóa tập tin khỏi máy chủ!!");
4057
4301
  // Fake vì hiện api xóa đang có vấn đề.
4058
- const newObjStorageList = this.objStorageList().filter(item => item.id !== id);
4302
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== id);
4059
4303
  this.objStorageList.set(newObjStorageList);
4060
4304
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
4061
4305
  // Fake vì hiện api xóa đang có vấn đề.
@@ -4074,51 +4318,85 @@ class MultiImageUploadComponent extends BaseValueAccessor {
4074
4318
  useExisting: forwardRef(() => MultiImageUploadComponent),
4075
4319
  multi: true,
4076
4320
  },
4077
- ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <div class=\"upload\">\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\">\r\n T\u1EA3i l\u00EAn\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n <span>T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} h\u00ECnh \u1EA3nh.</span>\r\n </nz-upload>\r\n </div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"imageGroupTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n<ng-template #imageGroupTpl>\r\n <nz-image-group>\r\n <div class=\"image-list\">\r\n @for (file of allFiles(); track file.objStorage?.id; let i = $index) {\r\n <div\r\n class=\"image-card-container\"\r\n [ngStyle]=\"{\r\n width: fieldSignal().imageCardWidth,\r\n height: fieldSignal().imageCardHeight,\r\n }\"\r\n >\r\n <div class=\"image-item\">\r\n <div class=\"image-item-info\">\r\n <img\r\n [id]=\"file.objStorage.id\"\r\n nz-image\r\n nzSrc=\"{{ file.previewUrl }}\"\r\n alt=\"{{ file.objStorage.originalName }}\"\r\n />\r\n </div>\r\n\r\n <div class=\"image-item-actions\">\r\n <button nz-button nzType=\"text\" nzSize=\"small\" (click)=\"handlePreview(file)\">\r\n <nz-icon nzType=\"eye\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n @if (isAddOrEdit()) {\r\n <button nz-button nzType=\"text\" nzSize=\"small\" (click)=\"handleDelete(file)\">\r\n <nz-icon nzType=\"delete\" nzTheme=\"outline\" />\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </nz-image-group>\r\n</ng-template>\r\n", styles: [".upload{margin-bottom:.625rem}.upload nz-upload .btn-upload{margin-right:.75rem}.image-list{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto}.image-list .image-card-container{display:inline-block;margin:0px .5rem .5rem 0;flex:0 0 auto}.image-list .image-card-container .image-item{padding:.5rem;border:1px solid #cbd5e1;height:100%;cursor:pointer;position:relative}.image-list .image-card-container .image-item .image-item-info{position:relative;height:100%;overflow:hidden}.image-list .image-card-container .image-item .image-item-info img{display:block;width:100%;height:100%;object-fit:contain}.image-list .image-card-container .image-item .image-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:\" \"}.image-list .image-card-container .image-item .image-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);transition:all .5s;opacity:0}.image-list .image-card-container .image-item .image-item-actions>button nz-icon{font-size:1rem!important;color:#fdfdfdd9}.image-list .image-card-container .image-item:hover .image-item-info:before,.image-list .image-card-container .image-item:hover .image-item-actions{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzImageModule }, { kind: "directive", type: i7.NzImageDirective, selector: "img[nz-image]", inputs: ["nzSrc", "nzSrcset", "nzDisablePreview", "nzFallback", "nzPlaceholder", "nzScaleStep"], exportAs: ["nzImage"] }, { kind: "component", type: i7.NzImageGroupComponent, selector: "nz-image-group", inputs: ["nzScaleStep"], exportAs: ["nzImageGroup"] }] }); }
4321
+ ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <div class=\"upload\">\r\n <nz-upload\r\n [nzDisabled]=\"\r\n allFiles().length >= field().maxFileCount ||\r\n disabledSignal() ||\r\n uploading()\r\n \"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" [nzLoading]=\"uploading()\">\r\n T\u1EA3i l\u00EAn\r\n @if (!uploading()) {\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n }\r\n </button>\r\n\r\n <span class=\"ms-2 text-muted fs-12\">\r\n (\u0110\u00E3 t\u1EA3i l\u00EAn {{ allFiles().length }}/{{ field().maxFileCount }}\r\n h\u00ECnh \u1EA3nh)\r\n </span>\r\n </nz-upload>\r\n </div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"imageGroupTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n<ng-template #imageGroupTpl>\r\n <nz-image-group>\r\n <div class=\"image-list\">\r\n @for (file of allFiles(); track file.objStorage?.id; let i = $index) {\r\n <div\r\n class=\"image-card-container\"\r\n [ngStyle]=\"{\r\n width: field().imageCardWidth,\r\n height: field().imageCardHeight,\r\n }\"\r\n >\r\n <div class=\"image-item\">\r\n <div class=\"image-item-info\">\r\n <img\r\n [id]=\"file.objStorage.id\"\r\n nz-image\r\n nzSrc=\"{{ file.previewUrl }}\"\r\n alt=\"{{ file.objStorage.originalName }}\"\r\n />\r\n </div>\r\n\r\n <div class=\"image-item-actions\">\r\n <button\r\n nz-button\r\n nzType=\"text\"\r\n nzSize=\"small\"\r\n (click)=\"handlePreview(file)\"\r\n >\r\n <nz-icon nzType=\"eye\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n @if (isAddOrEdit()) {\r\n <button\r\n nz-button\r\n nzType=\"text\"\r\n nzSize=\"small\"\r\n (click)=\"handleDelete(file)\"\r\n >\r\n <nz-icon nzType=\"delete\" nzTheme=\"outline\" />\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </nz-image-group>\r\n</ng-template>\r\n", styles: [".upload{margin-bottom:.625rem}.upload nz-upload .btn-upload{margin-right:.75rem}.image-list{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto}.image-list .image-card-container{display:inline-block;margin:0px .5rem .5rem 0;flex:0 0 auto}.image-list .image-card-container .image-item{padding:.5rem;border:1px solid #cbd5e1;height:100%;cursor:pointer;position:relative}.image-list .image-card-container .image-item .image-item-info{position:relative;height:100%;overflow:hidden}.image-list .image-card-container .image-item .image-item-info img{display:block;width:100%;height:100%;object-fit:contain}.image-list .image-card-container .image-item .image-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:\" \"}.image-list .image-card-container .image-item .image-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);transition:all .5s;opacity:0}.image-list .image-card-container .image-item .image-item-actions>button nz-icon{font-size:1rem!important;color:#fdfdfdd9}.image-list .image-card-container .image-item:hover .image-item-info:before,.image-list .image-card-container .image-item:hover .image-item-actions{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzImageModule }, { kind: "directive", type: i7.NzImageDirective, selector: "img[nz-image]", inputs: ["nzSrc", "nzSrcset", "nzDisablePreview", "nzFallback", "nzPlaceholder", "nzScaleStep"], exportAs: ["nzImage"] }, { kind: "component", type: i7.NzImageGroupComponent, selector: "nz-image-group", inputs: ["nzScaleStep"], exportAs: ["nzImageGroup"] }] }); }
4078
4322
  }
4079
4323
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiImageUploadComponent, decorators: [{
4080
4324
  type: Component,
4081
- args: [{ selector: 'app-multi-image-upload', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$3], providers: [
4325
+ args: [{ selector: "app-multi-image-upload", standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$1], providers: [
4082
4326
  {
4083
4327
  provide: NG_VALUE_ACCESSOR,
4084
4328
  useExisting: forwardRef(() => MultiImageUploadComponent),
4085
4329
  multi: true,
4086
4330
  },
4087
- ], template: "@if (isAddOrEdit()) {\r\n <div class=\"upload\">\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\">\r\n T\u1EA3i l\u00EAn\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n <span>T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} h\u00ECnh \u1EA3nh.</span>\r\n </nz-upload>\r\n </div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"imageGroupTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n<ng-template #imageGroupTpl>\r\n <nz-image-group>\r\n <div class=\"image-list\">\r\n @for (file of allFiles(); track file.objStorage?.id; let i = $index) {\r\n <div\r\n class=\"image-card-container\"\r\n [ngStyle]=\"{\r\n width: fieldSignal().imageCardWidth,\r\n height: fieldSignal().imageCardHeight,\r\n }\"\r\n >\r\n <div class=\"image-item\">\r\n <div class=\"image-item-info\">\r\n <img\r\n [id]=\"file.objStorage.id\"\r\n nz-image\r\n nzSrc=\"{{ file.previewUrl }}\"\r\n alt=\"{{ file.objStorage.originalName }}\"\r\n />\r\n </div>\r\n\r\n <div class=\"image-item-actions\">\r\n <button nz-button nzType=\"text\" nzSize=\"small\" (click)=\"handlePreview(file)\">\r\n <nz-icon nzType=\"eye\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n @if (isAddOrEdit()) {\r\n <button nz-button nzType=\"text\" nzSize=\"small\" (click)=\"handleDelete(file)\">\r\n <nz-icon nzType=\"delete\" nzTheme=\"outline\" />\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </nz-image-group>\r\n</ng-template>\r\n", styles: [".upload{margin-bottom:.625rem}.upload nz-upload .btn-upload{margin-right:.75rem}.image-list{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto}.image-list .image-card-container{display:inline-block;margin:0px .5rem .5rem 0;flex:0 0 auto}.image-list .image-card-container .image-item{padding:.5rem;border:1px solid #cbd5e1;height:100%;cursor:pointer;position:relative}.image-list .image-card-container .image-item .image-item-info{position:relative;height:100%;overflow:hidden}.image-list .image-card-container .image-item .image-item-info img{display:block;width:100%;height:100%;object-fit:contain}.image-list .image-card-container .image-item .image-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:\" \"}.image-list .image-card-container .image-item .image-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);transition:all .5s;opacity:0}.image-list .image-card-container .image-item .image-item-actions>button nz-icon{font-size:1rem!important;color:#fdfdfdd9}.image-list .image-card-container .image-item:hover .image-item-info:before,.image-list .image-card-container .image-item:hover .image-item-actions{opacity:1}\n"] }]
4331
+ ], template: "@if (isAddOrEdit()) {\r\n <div class=\"upload\">\r\n <nz-upload\r\n [nzDisabled]=\"\r\n allFiles().length >= field().maxFileCount ||\r\n disabledSignal() ||\r\n uploading()\r\n \"\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" [nzLoading]=\"uploading()\">\r\n T\u1EA3i l\u00EAn\r\n @if (!uploading()) {\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n }\r\n </button>\r\n\r\n <span class=\"ms-2 text-muted fs-12\">\r\n (\u0110\u00E3 t\u1EA3i l\u00EAn {{ allFiles().length }}/{{ field().maxFileCount }}\r\n h\u00ECnh \u1EA3nh)\r\n </span>\r\n </nz-upload>\r\n </div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"imageGroupTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch h\u00ECnh \u1EA3nh -->\r\n<ng-template #imageGroupTpl>\r\n <nz-image-group>\r\n <div class=\"image-list\">\r\n @for (file of allFiles(); track file.objStorage?.id; let i = $index) {\r\n <div\r\n class=\"image-card-container\"\r\n [ngStyle]=\"{\r\n width: field().imageCardWidth,\r\n height: field().imageCardHeight,\r\n }\"\r\n >\r\n <div class=\"image-item\">\r\n <div class=\"image-item-info\">\r\n <img\r\n [id]=\"file.objStorage.id\"\r\n nz-image\r\n nzSrc=\"{{ file.previewUrl }}\"\r\n alt=\"{{ file.objStorage.originalName }}\"\r\n />\r\n </div>\r\n\r\n <div class=\"image-item-actions\">\r\n <button\r\n nz-button\r\n nzType=\"text\"\r\n nzSize=\"small\"\r\n (click)=\"handlePreview(file)\"\r\n >\r\n <nz-icon nzType=\"eye\" nzTheme=\"outline\" />\r\n </button>\r\n\r\n @if (isAddOrEdit()) {\r\n <button\r\n nz-button\r\n nzType=\"text\"\r\n nzSize=\"small\"\r\n (click)=\"handleDelete(file)\"\r\n >\r\n <nz-icon nzType=\"delete\" nzTheme=\"outline\" />\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </nz-image-group>\r\n</ng-template>\r\n", styles: [".upload{margin-bottom:.625rem}.upload nz-upload .btn-upload{margin-right:.75rem}.image-list{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto}.image-list .image-card-container{display:inline-block;margin:0px .5rem .5rem 0;flex:0 0 auto}.image-list .image-card-container .image-item{padding:.5rem;border:1px solid #cbd5e1;height:100%;cursor:pointer;position:relative}.image-list .image-card-container .image-item .image-item-info{position:relative;height:100%;overflow:hidden}.image-list .image-card-container .image-item .image-item-info img{display:block;width:100%;height:100%;object-fit:contain}.image-list .image-card-container .image-item .image-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:\" \"}.image-list .image-card-container .image-item .image-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);transition:all .5s;opacity:0}.image-list .image-card-container .image-item .image-item-actions>button nz-icon{font-size:1rem!important;color:#fdfdfdd9}.image-list .image-card-container .image-item:hover .image-item-info:before,.image-list .image-card-container .image-item:hover .image-item-actions{opacity:1}\n"] }]
4088
4332
  }], ctorParameters: () => [] });
4089
4333
 
4090
- const ZORRO_MODULES$2 = [NzUploadModule, NzIconModule, NzInputModule, NzButtonModule, NzSpinModule];
4091
- const COMPONENTS$2 = [SuffixComponent];
4334
+ const modules$1 = [CommonModule];
4335
+ const zorroModules$1 = [
4336
+ NzUploadModule,
4337
+ NzIconModule,
4338
+ NzInputModule,
4339
+ NzButtonModule,
4340
+ NzSpinModule,
4341
+ ];
4342
+ const components$1 = [SuffixComponent];
4092
4343
  class SingleFileUploadComponent extends BaseValueAccessor {
4093
4344
  constructor() {
4094
4345
  super();
4095
- this.fieldSignal = computed(() => {
4346
+ this.field = computed(() => {
4096
4347
  const base = this.fieldInput();
4097
4348
  if (!(base instanceof SingleFileUploadField) ||
4098
- (base.type !== FieldTypeEnum.SingleFileUpload && base.controlType !== ControlTypeEnum.Upload)) {
4099
- throw new Error('Expected SingleFileUploadField');
4349
+ (base.type !== FieldTypeEnum.SingleFileUpload &&
4350
+ base.controlType !== ControlTypeEnum.Upload)) {
4351
+ throw new Error("Expected SingleFileUploadField");
4100
4352
  }
4101
4353
  return base;
4102
4354
  });
4355
+ // Inject services
4356
+ this.fileService = inject(FileService);
4357
+ this.nzNotification = inject(NzNotificationService);
4358
+ this.formConfigService = inject(FormConfigService);
4103
4359
  // Mảng lưu trữ objectStorage liên quan đến tập tin.
4104
4360
  this.objStorageList = signal([]);
4105
4361
  // Mảng đại diện các files.
4106
4362
  this.allFiles = signal([]);
4107
4363
  // Text hướng dẫn các loại extension được upload.
4108
- this.fileExtensionGuide = signal('');
4109
- this.inputDisplayValue = '';
4364
+ this.fileExtensionGuide = computed(() => {
4365
+ if (!this.isAddOrEdit())
4366
+ return "";
4367
+ const { acceptedMimeTypes } = this.field();
4368
+ return `Hỗ trợ định dạng ${getExtensions(acceptedMimeTypes).join(", ")}`;
4369
+ });
4370
+ this.viewDisplayValue = computed(() => {
4371
+ // 1. Nếu không phải View thì không cần quan tâm text này
4372
+ if (!this.isView())
4373
+ return "";
4374
+ if (this.allFiles().length === 0)
4375
+ return NO_INFO_TEXT;
4376
+ const { objStorage } = this.allFiles()[0];
4377
+ const { nameAlias, name } = objStorage;
4378
+ if (!nameAlias && !name)
4379
+ return NO_INFO_TEXT;
4380
+ return nameAlias ?? name;
4381
+ });
4382
+ this.addEditDisplayValue = computed(() => {
4383
+ if (!this.isAddOrEdit())
4384
+ return "";
4385
+ if (this.allFiles().length === 0)
4386
+ return UPLOAD_FILE_TEXT;
4387
+ const { objStorage } = this.allFiles()[0];
4388
+ const { nameAlias, name } = objStorage;
4389
+ if (!nameAlias && !name)
4390
+ return NO_FILE_NAME;
4391
+ return nameAlias ?? name;
4392
+ });
4110
4393
  this.uploading = signal(false);
4111
- this.fileService = inject(FileService);
4112
- this.toast = inject(ToastrService);
4113
- this.translate = inject(TranslateService);
4114
- this.nzNotification = inject(NzNotificationService);
4115
- this.formConfigService = inject(FormConfigService);
4116
4394
  this.beforeUpload = (file, fileList) => {
4117
4395
  // const originFile = file?.originFileObj as unknown as File; // có thể ở version mới nhất đã fix lỗi NzUploadFile nhưng trả về File của tham số file.
4118
4396
  // Xử lý gán vào CVA để tiến hành validation theo các props đã truyền vào.
4119
4397
  const originFile = file;
4120
4398
  this.propagateValue(originFile);
4121
- const { maxSizeInMB, acceptedMimeTypes } = this.fieldSignal();
4399
+ const { maxSizeInMB, acceptedMimeTypes } = this.field();
4122
4400
  const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
4123
4401
  const fileType = file.type;
4124
4402
  if (fileSizeInMB > maxSizeInMB || !acceptedMimeTypes.includes(fileType)) {
@@ -4136,27 +4414,6 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4136
4414
  // Validation đã thỏa, tiến hành upload file.
4137
4415
  return this.uploadFile(originFile);
4138
4416
  };
4139
- // 1. Effect chung Add + Edit
4140
- effect(() => {
4141
- if (this.isAddOrEdit()) {
4142
- const field = untracked(() => this.fieldSignal());
4143
- const { acceptedMimeTypes } = field;
4144
- this.fileExtensionGuide.set(`Hỗ trợ định dạng ${getExtensions(acceptedMimeTypes).join(', ')}`);
4145
- }
4146
- });
4147
- // Trigger reset từ bên ngoài form.
4148
- effect(() => {
4149
- const reset = this.dFormService.resetTrigger();
4150
- if (reset?.formId === this.formId) {
4151
- this.reset();
4152
- }
4153
- });
4154
- effect(() => {
4155
- const allFiles = this.allFiles();
4156
- this.inputDisplayValue = allFiles[0]
4157
- ? (allFiles[0].objStorage.nameAlias ?? allFiles[0].objStorage.name) || 'Không có thông tin'
4158
- : 'Không có thông tin';
4159
- });
4160
4417
  }
4161
4418
  // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
4162
4419
  writeValueToView(value) {
@@ -4176,7 +4433,7 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4176
4433
  const objStorageList = JSON.parse(value);
4177
4434
  this.objStorageList.set(objStorageList);
4178
4435
  // Khởi tạo danh sách MyUploadFile từ data json.
4179
- const myUploadFiles = objStorageList.map(objStorage => ({
4436
+ const myUploadFiles = objStorageList.map((objStorage) => ({
4180
4437
  objStorage,
4181
4438
  isTemp: false,
4182
4439
  previewUrl: `${this.formConfigService.getStorageUrl()}${objStorage.bucket}/${objStorage.path}`,
@@ -4186,14 +4443,18 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4186
4443
  else {
4187
4444
  if (value) {
4188
4445
  this.allFiles.set([
4189
- { isTemp: false, objStorage: { id: '', name: value }, previewUrl: '' },
4446
+ {
4447
+ isTemp: false,
4448
+ objStorage: { id: "", name: value },
4449
+ previewUrl: "",
4450
+ },
4190
4451
  ]);
4191
4452
  }
4192
4453
  }
4193
4454
  }
4194
4455
  uploadFile(file) {
4195
4456
  this.uploading.set(true);
4196
- const { apiFileConfig, uploadTemp } = this.fieldSignal();
4457
+ const { apiFileConfig, uploadTemp } = this.field();
4197
4458
  const newFileName = FormUtils.randomFileName(file.name);
4198
4459
  const mimeCfg = apiFileConfig.perMimeTypeConfig?.[file.type]; // Lấy mimeTypes file upload.
4199
4460
  const uploadUrl = mimeCfg?.uploadUrl ?? apiFileConfig.uploadUrl; // Nếu có config upload riêng cho mimeTypes đang kiểm tra.
@@ -4204,23 +4465,23 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4204
4465
  : (() => {
4205
4466
  // Mặc định
4206
4467
  const fd = new FormData();
4207
- fd.append('File', file, newFileName);
4468
+ fd.append("File", file, newFileName);
4208
4469
  if (apiFileConfig.uploadByPath && apiFileConfig.uploadPath) {
4209
- fd.append('path', apiFileConfig.uploadPath);
4470
+ fd.append("path", apiFileConfig.uploadPath);
4210
4471
  }
4211
4472
  if (apiFileConfig.isOriginName) {
4212
- fd.append('nameAlias', file.name);
4473
+ fd.append("nameAlias", file.name);
4213
4474
  }
4214
4475
  // fd.append('isTemp', 'true');
4215
4476
  return fd;
4216
4477
  })();
4217
4478
  if (uploadTemp)
4218
- formData.append('isTemp', 'true');
4479
+ formData.append("isTemp", "true");
4219
4480
  return this.fileService
4220
4481
  .uploadFile(file, formData, { uploadUrl })
4221
4482
  .pipe(this.takeUntilDestroy(), finalize(() => this.uploading.set(false)))
4222
4483
  .subscribe({
4223
- next: res => {
4484
+ next: (res) => {
4224
4485
  const { code, data, message } = res;
4225
4486
  if (code === 0) {
4226
4487
  this.objStorageList.set([data]);
@@ -4229,7 +4490,7 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4229
4490
  objStorage: data,
4230
4491
  isTemp: true,
4231
4492
  previewUrl: uploadTemp
4232
- ? this.formConfigService.getViewFileUrl(data.name ?? '', true)
4493
+ ? this.formConfigService.getViewFileUrl(data.name ?? "", true)
4233
4494
  : `${this.formConfigService.getStorageUrl()}${data.bucket}/${data.path}`,
4234
4495
  },
4235
4496
  ]);
@@ -4240,19 +4501,19 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4240
4501
  }
4241
4502
  },
4242
4503
  error: () => {
4243
- console.error('Lỗi trong quá trình tải tập tin đến máy chủ!!');
4504
+ console.error(UPLOAD_FILE_ERROR);
4244
4505
  },
4245
4506
  });
4246
4507
  }
4247
4508
  handleDelete(file) {
4248
4509
  const { objStorage } = file;
4249
- const { uploadTemp } = this.fieldSignal();
4510
+ const { uploadTemp } = this.field();
4250
4511
  this.objStorageList.set([]);
4251
4512
  // Loại bỏ và cập nhật danh sách MyUploadFile.
4252
- const newAllFiles = this.allFiles().filter(item => item.objStorage?.id !== objStorage?.id);
4513
+ const newAllFiles = this.allFiles().filter((item) => item.objStorage?.id !== objStorage?.id);
4253
4514
  this.allFiles.set(newAllFiles);
4254
4515
  if (!uploadTemp) {
4255
- this.deleteObjStorage(objStorage.id || '');
4516
+ this.deleteObjStorage(objStorage.id || "");
4256
4517
  return;
4257
4518
  }
4258
4519
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
@@ -4260,10 +4521,12 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4260
4521
  }
4261
4522
  deleteObjStorage(id) {
4262
4523
  return this.fileService
4263
- .deleteObjStorage(id, { deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl })
4524
+ .deleteObjStorage(id, {
4525
+ deleteUrl: this.field().apiFileConfig.deleteUrl,
4526
+ })
4264
4527
  .pipe(this.takeUntilDestroy())
4265
4528
  .subscribe({
4266
- next: res => {
4529
+ next: (res) => {
4267
4530
  const { code, message } = res;
4268
4531
  if (code === 0) {
4269
4532
  this.objStorageList.set([]);
@@ -4274,7 +4537,7 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4274
4537
  }
4275
4538
  },
4276
4539
  error: () => {
4277
- console.error('Lỗi trong quá trình xóa tập tin khỏi máy chủ!!');
4540
+ console.error(DELETE_FILE_ERROR);
4278
4541
  // Tạm do api xóa có vấn đề
4279
4542
  this.objStorageList.set([]);
4280
4543
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
@@ -4288,23 +4551,24 @@ class SingleFileUploadComponent extends BaseValueAccessor {
4288
4551
  useExisting: forwardRef(() => SingleFileUploadComponent),
4289
4552
  multi: true,
4290
4553
  },
4291
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ inputDisplayValue }}</span>\r\n }\r\n }\r\n @if (isAddOrEdit()) {\r\n <nz-spin [nzSpinning]=\"uploading()\" nzTip=\"\u0110ang t\u1EA3i l\u00EAn\" class=\"width-100\">\r\n <div class=\"d-flex\">\r\n <nz-input-group\r\n [nzSuffix]=\"allFiles().length > 0 && isAddOrEdit() ? deleteIcon : undefined\"\r\n class=\"flex-1\"\r\n >\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"no-events\" />\r\n </nz-input-group>\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload height-100 text-white\">\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n </nz-upload>\r\n </div>\r\n </nz-spin>\r\n }\r\n\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n@if (fieldSignal().showAcceptedExtensionGuide && fileExtensionGuide()) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <!-- <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container> -->\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles() | slice: 0 : 1; track file.objStorage?.id) {\r\n <li class=\"upload-item\">\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n\r\n <span class=\"filename\">{{ file.objStorage.name }}</span>\r\n\r\n @if (isAddOrEdit()) {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file)\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #deleteIcon>\r\n <span\r\n title=\"X\u00F3a\"\r\n class=\"mdi mdi-delete cursor-pointer text-error\"\r\n (click)=\"handleDelete(allFiles()[0])\"\r\n ></span>\r\n</ng-template>\r\n", styles: [".upload{display:flex}.file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .paper-clip-icon{margin-right:.5rem}.custom-upload-list .delete-icon{color:red;cursor:pointer;margin-left:.5rem}.file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.btn-upload{background-color:#2666ab}.modeView{pointer-events:none;color:#575757;font-size:12px}.no-events{pointer-events:none;border-radius:4px!important;color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "pipe", type: i1$2.SlicePipe, name: "slice" }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: i1$1.NzInputGroupComponent, selector: "nz-input-group", inputs: ["nzAddOnBeforeIcon", "nzAddOnAfterIcon", "nzPrefixIcon", "nzSuffixIcon", "nzAddOnBefore", "nzAddOnAfter", "nzPrefix", "nzStatus", "nzSuffix", "nzSize", "nzSearch", "nzCompact"], exportAs: ["nzInputGroup"] }, { kind: "directive", type: i1$1.NzInputGroupWhitSuffixOrPrefixDirective, selector: "nz-input-group[nzSuffix], nz-input-group[nzPrefix]" }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
4554
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"viewDisplayValue()\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ viewDisplayValue() }}</span>\r\n }\r\n }\r\n @if (isAddOrEdit()) {\r\n <div class=\"flex w-100\">\r\n <nz-input-group\r\n [nzSuffix]=\"\r\n uploading() || (allFiles().length > 0 && isAddOrEdit())\r\n ? actionIcon\r\n : undefined\r\n \"\r\n class=\"flex-1\"\r\n >\r\n <input\r\n nz-input\r\n [value]=\"\r\n uploading() ? '\u0110ang t\u1EA3i l\u00EAn t\u1EADp tin...' : addEditDisplayValue()\r\n \"\r\n class=\"no-events\"\r\n />\r\n </nz-input-group>\r\n\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button\r\n nz-button\r\n class=\"btn-upload h-100 text-white\"\r\n [disabled]=\"uploading()\"\r\n >\r\n <span class=\"fs-12\">T\u1EA3i l\u00EAn</span>\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n </nz-upload>\r\n </div>\r\n }\r\n\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n\r\n<ng-template #actionIcon>\r\n @if (uploading()) {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"text-primary\"\r\n ></span>\r\n } @else if (allFiles().length > 0 && isAddOrEdit()) {\r\n <span\r\n title=\"X\u00F3a\"\r\n class=\"mdi mdi-delete pointer text-danger\"\r\n (click)=\"handleDelete(allFiles()[0])\"\r\n ></span>\r\n }\r\n</ng-template>\r\n\r\n@if (field().showAcceptedExtensionGuide && fileExtensionGuide()) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<!-- @if (allFiles().length > 0) { -->\r\n<!-- <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container> -->\r\n<!-- } -->\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<!-- <ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles() | slice: 0 : 1; track file.objStorage?.id) {\r\n <li class=\"upload-item\">\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n\r\n <span class=\"filename\">{{ file.objStorage.name }}</span>\r\n\r\n @if (isAddOrEdit()) {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file)\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template> -->\r\n", styles: [".file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .paper-clip-icon{margin-right:.5rem}.custom-upload-list .delete-icon{color:red;cursor:pointer;margin-left:.5rem}.file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.btn-upload{background-color:#2666ab}.no-events{pointer-events:none;color:#cdbfbf}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: i1$1.NzInputGroupComponent, selector: "nz-input-group", inputs: ["nzAddOnBeforeIcon", "nzAddOnAfterIcon", "nzPrefixIcon", "nzSuffixIcon", "nzAddOnBefore", "nzAddOnAfter", "nzPrefix", "nzStatus", "nzSuffix", "nzSize", "nzSearch", "nzCompact"], exportAs: ["nzInputGroup"] }, { kind: "directive", type: i1$1.NzInputGroupWhitSuffixOrPrefixDirective, selector: "nz-input-group[nzSuffix], nz-input-group[nzPrefix]" }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
4292
4555
  }
4293
4556
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SingleFileUploadComponent, decorators: [{
4294
4557
  type: Component,
4295
- args: [{ selector: 'app-single-file-upload', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$2, ...COMPONENTS$2], providers: [
4558
+ args: [{ selector: "app-single-file-upload", standalone: true, imports: [...modules$1, ...zorroModules$1, ...components$1], providers: [
4296
4559
  {
4297
4560
  provide: NG_VALUE_ACCESSOR,
4298
4561
  useExisting: forwardRef(() => SingleFileUploadComponent),
4299
4562
  multi: true,
4300
4563
  },
4301
- ], template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ inputDisplayValue }}</span>\r\n }\r\n }\r\n @if (isAddOrEdit()) {\r\n <nz-spin [nzSpinning]=\"uploading()\" nzTip=\"\u0110ang t\u1EA3i l\u00EAn\" class=\"width-100\">\r\n <div class=\"d-flex\">\r\n <nz-input-group\r\n [nzSuffix]=\"allFiles().length > 0 && isAddOrEdit() ? deleteIcon : undefined\"\r\n class=\"flex-1\"\r\n >\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"no-events\" />\r\n </nz-input-group>\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload height-100 text-white\">\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n </nz-upload>\r\n </div>\r\n </nz-spin>\r\n }\r\n\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n@if (fieldSignal().showAcceptedExtensionGuide && fileExtensionGuide()) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <!-- <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container> -->\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles() | slice: 0 : 1; track file.objStorage?.id) {\r\n <li class=\"upload-item\">\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n\r\n <span class=\"filename\">{{ file.objStorage.name }}</span>\r\n\r\n @if (isAddOrEdit()) {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file)\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #deleteIcon>\r\n <span\r\n title=\"X\u00F3a\"\r\n class=\"mdi mdi-delete cursor-pointer text-error\"\r\n (click)=\"handleDelete(allFiles()[0])\"\r\n ></span>\r\n</ng-template>\r\n", styles: [".upload{display:flex}.file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .paper-clip-icon{margin-right:.5rem}.custom-upload-list .delete-icon{color:red;cursor:pointer;margin-left:.5rem}.file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.btn-upload{background-color:#2666ab}.modeView{pointer-events:none;color:#575757;font-size:12px}.no-events{pointer-events:none;border-radius:4px!important;color:#575757;font-size:12px}\n"] }]
4564
+ ], template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (viewMode() === viewModeEnum.form) {\r\n <input nz-input [value]=\"viewDisplayValue()\" class=\"modeView\" />\r\n }\r\n @if (viewMode() === viewModeEnum.text) {\r\n <span class=\"modeView\">{{ viewDisplayValue() }}</span>\r\n }\r\n }\r\n @if (isAddOrEdit()) {\r\n <div class=\"flex w-100\">\r\n <nz-input-group\r\n [nzSuffix]=\"\r\n uploading() || (allFiles().length > 0 && isAddOrEdit())\r\n ? actionIcon\r\n : undefined\r\n \"\r\n class=\"flex-1\"\r\n >\r\n <input\r\n nz-input\r\n [value]=\"\r\n uploading() ? '\u0110ang t\u1EA3i l\u00EAn t\u1EADp tin...' : addEditDisplayValue()\r\n \"\r\n class=\"no-events\"\r\n />\r\n </nz-input-group>\r\n\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"false\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button\r\n nz-button\r\n class=\"btn-upload h-100 text-white\"\r\n [disabled]=\"uploading()\"\r\n >\r\n <span class=\"fs-12\">T\u1EA3i l\u00EAn</span>\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n </button>\r\n </nz-upload>\r\n </div>\r\n }\r\n\r\n @if (field().suffixes.length > 0) {\r\n @for (suffix of field().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n\r\n<ng-template #actionIcon>\r\n @if (uploading()) {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"text-primary\"\r\n ></span>\r\n } @else if (allFiles().length > 0 && isAddOrEdit()) {\r\n <span\r\n title=\"X\u00F3a\"\r\n class=\"mdi mdi-delete pointer text-danger\"\r\n (click)=\"handleDelete(allFiles()[0])\"\r\n ></span>\r\n }\r\n</ng-template>\r\n\r\n@if (field().showAcceptedExtensionGuide && fileExtensionGuide()) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<!-- @if (allFiles().length > 0) { -->\r\n<!-- <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container> -->\r\n<!-- } -->\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<!-- <ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles() | slice: 0 : 1; track file.objStorage?.id) {\r\n <li class=\"upload-item\">\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n\r\n <span class=\"filename\">{{ file.objStorage.name }}</span>\r\n\r\n @if (isAddOrEdit()) {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file)\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template> -->\r\n", styles: [".file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .paper-clip-icon{margin-right:.5rem}.custom-upload-list .delete-icon{color:red;cursor:pointer;margin-left:.5rem}.file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.btn-upload{background-color:#2666ab}.no-events{pointer-events:none;color:#cdbfbf}\n"] }]
4302
4565
  }], ctorParameters: () => [] });
4303
4566
 
4304
4567
  // Các field component đại diện cho giao diện của từng control.
4305
4568
  const FIELD_COMPONENTS = [
4306
4569
  InputTextComponent,
4307
4570
  InputPasswordComponent,
4571
+ InputFormattedComponent,
4308
4572
  SingleSelectComponent,
4309
4573
  MultiSelectComponent,
4310
4574
  TreeSelectComponent,
@@ -4348,8 +4612,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
4348
4612
  }]
4349
4613
  }], ctorParameters: () => [] });
4350
4614
 
4351
- const ZORRO_MODULES$1 = [NzFormModule, NzGridModule];
4352
- const COMPONENTS$1 = [...FIELD_COMPONENTS];
4615
+ class FieldUserConfigComponent {
4616
+ constructor() {
4617
+ this.field = input.required();
4618
+ this.control = input.required();
4619
+ }
4620
+ onToggleUserRequired(isRequired) {
4621
+ this.field().required = isRequired;
4622
+ const ctrl = this.control();
4623
+ if (isRequired) {
4624
+ ctrl.addValidators(Validators.required);
4625
+ }
4626
+ else {
4627
+ ctrl.removeValidators(Validators.required);
4628
+ }
4629
+ // Cập nhật lại trạng thái validation ngay lập tức
4630
+ ctrl.updateValueAndValidity();
4631
+ }
4632
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldUserConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4633
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: FieldUserConfigComponent, isStandalone: true, selector: "app-field-user-config", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!-- C\u1EA5u h\u00ECnh (Dropdown & Menu) ch\u1EC9 hi\u1EC7n khi c\u00F3 b\u1EA5t k\u1EF3 config n\u00E0o -->\r\n@if (field().userConfigs) {\r\n <!-- Trigger N\u00FAt C\u1EA5u h\u00ECnh -->\r\n <a\r\n nz-dropdown\r\n [nzDropdownMenu]=\"configMenu\"\r\n nzTrigger=\"click\"\r\n [nzClickHide]=\"false\"\r\n [title]=\"'C\u1EA5u h\u00ECnh'\"\r\n nzPlacement=\"bottomRight\"\r\n >\r\n <span\r\n nz-icon\r\n nzType=\"setting\"\r\n nzTheme=\"outline\"\r\n class=\"config-trigger-icon\"\r\n ></span>\r\n </a>\r\n\r\n <!-- Menu C\u1EA5u h\u00ECnh -->\r\n <nz-dropdown-menu #configMenu=\"nzDropdownMenu\">\r\n <ul nz-menu>\r\n @if (field().userConfigs?.allowOverrideRequired) {\r\n <li nz-menu-item nzDisableItemHover class=\"config-menu-item\">\r\n <div class=\"config-item-content\">\r\n <span class=\"config-item-label\">B\u1EAFt bu\u1ED9c nh\u1EADp</span>\r\n <nz-switch\r\n nzSize=\"small\"\r\n [ngModel]=\"field().required\"\r\n [ngModelOptions]=\"{ standalone: true }\"\r\n (ngModelChange)=\"onToggleUserRequired($event)\"\r\n ></nz-switch>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </nz-dropdown-menu>\r\n}\r\n", styles: ["@charset \"UTF-8\";.config-trigger-icon{color:#bfbfbf;cursor:pointer;font-size:16px;transition:all .3s cubic-bezier(.645,.045,.355,1);display:inline-block}.config-trigger-icon:hover{color:#1890ff;transform:rotate(90deg) scale(1.1)}.config-menu-item{padding:8px 12px;cursor:default}.config-item-content{display:flex;justify-content:space-between;align-items:center;gap:16px;min-width:130px}.config-item-label{font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: NzDropDownModule }, { kind: "directive", type: i1$3.NzMenuDirective, selector: "[nz-menu]", inputs: ["nzInlineIndent", "nzTheme", "nzMode", "nzInlineCollapsed", "nzSelectable"], outputs: ["nzClick"], exportAs: ["nzMenu"] }, { kind: "component", type: i1$3.NzMenuItemComponent, selector: "[nz-menu-item]", inputs: ["nzPaddingLeft", "nzDisabled", "nzSelected", "nzDanger", "nzMatchRouterExact", "nzMatchRouter"], exportAs: ["nzMenuItem"] }, { kind: "directive", type: i2$3.NzDropDownDirective, selector: "[nz-dropdown]", inputs: ["nzDropdownMenu", "nzTrigger", "nzMatchWidthElement", "nzBackdrop", "nzClickHide", "nzDisabled", "nzVisible", "nzOverlayClassName", "nzOverlayStyle", "nzPlacement"], outputs: ["nzVisibleChange"], exportAs: ["nzDropdown"] }, { kind: "directive", type: i2$3.NzDropDownADirective, selector: "a[nz-dropdown]" }, { kind: "component", type: i2$3.NzDropdownMenuComponent, selector: "nz-dropdown-menu", exportAs: ["nzDropdownMenu"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzSwitchModule }, { kind: "component", type: i4$1.NzSwitchComponent, selector: "nz-switch", inputs: ["nzLoading", "nzDisabled", "nzControl", "nzCheckedChildren", "nzUnCheckedChildren", "nzSize", "nzId"], exportAs: ["nzSwitch"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
4634
+ }
4635
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FieldUserConfigComponent, decorators: [{
4636
+ type: Component,
4637
+ args: [{ selector: "app-field-user-config", standalone: true, imports: [NzDropDownModule, NzIconModule, NzSwitchModule, FormsModule], template: "<!-- C\u1EA5u h\u00ECnh (Dropdown & Menu) ch\u1EC9 hi\u1EC7n khi c\u00F3 b\u1EA5t k\u1EF3 config n\u00E0o -->\r\n@if (field().userConfigs) {\r\n <!-- Trigger N\u00FAt C\u1EA5u h\u00ECnh -->\r\n <a\r\n nz-dropdown\r\n [nzDropdownMenu]=\"configMenu\"\r\n nzTrigger=\"click\"\r\n [nzClickHide]=\"false\"\r\n [title]=\"'C\u1EA5u h\u00ECnh'\"\r\n nzPlacement=\"bottomRight\"\r\n >\r\n <span\r\n nz-icon\r\n nzType=\"setting\"\r\n nzTheme=\"outline\"\r\n class=\"config-trigger-icon\"\r\n ></span>\r\n </a>\r\n\r\n <!-- Menu C\u1EA5u h\u00ECnh -->\r\n <nz-dropdown-menu #configMenu=\"nzDropdownMenu\">\r\n <ul nz-menu>\r\n @if (field().userConfigs?.allowOverrideRequired) {\r\n <li nz-menu-item nzDisableItemHover class=\"config-menu-item\">\r\n <div class=\"config-item-content\">\r\n <span class=\"config-item-label\">B\u1EAFt bu\u1ED9c nh\u1EADp</span>\r\n <nz-switch\r\n nzSize=\"small\"\r\n [ngModel]=\"field().required\"\r\n [ngModelOptions]=\"{ standalone: true }\"\r\n (ngModelChange)=\"onToggleUserRequired($event)\"\r\n ></nz-switch>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n </nz-dropdown-menu>\r\n}\r\n", styles: ["@charset \"UTF-8\";.config-trigger-icon{color:#bfbfbf;cursor:pointer;font-size:16px;transition:all .3s cubic-bezier(.645,.045,.355,1);display:inline-block}.config-trigger-icon:hover{color:#1890ff;transform:rotate(90deg) scale(1.1)}.config-menu-item{padding:8px 12px;cursor:default}.config-item-content{display:flex;justify-content:space-between;align-items:center;gap:16px;min-width:130px}.config-item-label{font-size:13px}\n"] }]
4638
+ }] });
4639
+
4640
+ const modules = [CommonModule, FormsModule, ReactiveFormsModule];
4641
+ const zorroModules = [
4642
+ NzFormModule,
4643
+ NzGridModule,
4644
+ NzDropDownModule,
4645
+ NzIconModule,
4646
+ NzSwitchModule,
4647
+ NzFlexModule,
4648
+ ];
4649
+ const components = [...FIELD_COMPONENTS, FieldUserConfigComponent];
4353
4650
  class DynamicFieldComponent {
4354
4651
  constructor() {
4355
4652
  this.field = input.required();
@@ -4369,11 +4666,11 @@ class DynamicFieldComponent {
4369
4666
  return (this.validationService.getFirstError(this.control(), this.field(), this.fields()) ?? "");
4370
4667
  }
4371
4668
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4372
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFieldComponent, isStandalone: true, selector: "app-dynamic-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: true, transformFunction: null }, formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "component", type: i3$3.NzFormItemComponent, selector: "nz-form-item", exportAs: ["nzFormItem"] }, { kind: "component", type: i3$3.NzFormLabelComponent, selector: "nz-form-label", inputs: ["nzFor", "nzRequired", "nzNoColon", "nzTooltipTitle", "nzTooltipIcon", "nzLabelAlign", "nzLabelWrap"], exportAs: ["nzFormLabel"] }, { kind: "component", type: i3$3.NzFormControlComponent, selector: "nz-form-control", inputs: ["nzSuccessTip", "nzWarningTip", "nzErrorTip", "nzValidatingTip", "nzExtra", "nzAutoTips", "nzDisableAutoTips", "nzHasFeedback", "nzValidateStatus"], exportAs: ["nzFormControl"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "component", type: InputTextComponent, selector: "app-input-text" }, { kind: "component", type: InputPasswordComponent, selector: "app-input-password" }, { kind: "component", type: SingleSelectComponent, selector: "app-single-select", inputs: ["isCustomInputText"] }, { kind: "component", type: MultiSelectComponent, selector: "app-multi-select" }, { kind: "component", type: TreeSelectComponent, selector: "app-tree-select", inputs: ["isCustomInputText"] }, { kind: "component", type: TextareaComponent, selector: "app-textarea" }, { kind: "component", type: DatePickerComponent, selector: "app-date-picker" }, { kind: "component", type: DateRangeComponent, selector: "app-date-range" }, { kind: "component", type: SingleFileUploadComponent, selector: "app-single-file-upload" }, { kind: "component", type: MultiFileUploadComponent, selector: "app-multi-file-upload" }, { kind: "component", type: MultiImageUploadComponent, selector: "app-multi-image-upload" }, { kind: "component", type: AvatarUploadComponent, selector: "app-avatar-upload", inputs: ["size"] }] }); }
4669
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFieldComponent, isStandalone: true, selector: "app-dynamic-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: true, transformFunction: null }, formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n <div nz-flex [nzAlign]=\"'flex-start'\" [nzGap]=\"8\" class=\"w-100\">\r\n <div class=\"flex-1 min-w-0\">\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Input formatted -->\r\n @case (fieldTypeEnum.InputFormatted) {\r\n <app-input-formatted\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-formatted>\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- T\u00E1ch ph\u1EA7n c\u1EA5u h\u00ECnh ra Component ri\u00EAng -->\r\n @if (mode() !== formModeEnum.View) {\r\n <div class=\"field-user-config-ctn\">\r\n <app-field-user-config\r\n [field]=\"field()\"\r\n [control]=\"control()\"\r\n ></app-field-user-config>\r\n </div>\r\n }\r\n </div>\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$4.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$4.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "component", type: i3$3.NzFormItemComponent, selector: "nz-form-item", exportAs: ["nzFormItem"] }, { kind: "component", type: i3$3.NzFormLabelComponent, selector: "nz-form-label", inputs: ["nzFor", "nzRequired", "nzNoColon", "nzTooltipTitle", "nzTooltipIcon", "nzLabelAlign", "nzLabelWrap"], exportAs: ["nzFormLabel"] }, { kind: "component", type: i3$3.NzFormControlComponent, selector: "nz-form-control", inputs: ["nzSuccessTip", "nzWarningTip", "nzErrorTip", "nzValidatingTip", "nzExtra", "nzAutoTips", "nzDisableAutoTips", "nzHasFeedback", "nzValidateStatus"], exportAs: ["nzFormControl"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "ngmodule", type: NzDropDownModule }, { kind: "ngmodule", type: NzIconModule }, { kind: "ngmodule", type: NzSwitchModule }, { kind: "ngmodule", type: NzFlexModule }, { kind: "directive", type: i4$2.NzFlexDirective, selector: "[nz-flex],nz-flex", inputs: ["nzVertical", "nzJustify", "nzAlign", "nzGap", "nzWrap", "nzFlex"], exportAs: ["nzFlex"] }, { kind: "component", type: InputTextComponent, selector: "app-input-text" }, { kind: "component", type: InputPasswordComponent, selector: "app-input-password" }, { kind: "component", type: InputFormattedComponent, selector: "app-input-formatted" }, { kind: "component", type: SingleSelectComponent, selector: "app-single-select", inputs: ["isCustomInputText"] }, { kind: "component", type: MultiSelectComponent, selector: "app-multi-select" }, { kind: "component", type: TreeSelectComponent, selector: "app-tree-select", inputs: ["isCustomInputText"] }, { kind: "component", type: TextareaComponent, selector: "app-textarea" }, { kind: "component", type: DatePickerComponent, selector: "app-date-picker" }, { kind: "component", type: DateRangeComponent, selector: "app-date-range" }, { kind: "component", type: SingleFileUploadComponent, selector: "app-single-file-upload" }, { kind: "component", type: MultiFileUploadComponent, selector: "app-multi-file-upload" }, { kind: "component", type: MultiImageUploadComponent, selector: "app-multi-image-upload" }, { kind: "component", type: AvatarUploadComponent, selector: "app-avatar-upload", inputs: ["size"] }, { kind: "component", type: FieldUserConfigComponent, selector: "app-field-user-config", inputs: ["field", "control"] }] }); }
4373
4670
  }
4374
4671
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFieldComponent, decorators: [{
4375
4672
  type: Component,
4376
- args: [{ selector: "app-dynamic-field", standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$1, ...COMPONENTS$1], template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"] }]
4673
+ args: [{ selector: "app-dynamic-field", standalone: true, imports: [...modules, ...zorroModules, ...components], template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n <div nz-flex [nzAlign]=\"'flex-start'\" [nzGap]=\"8\" class=\"w-100\">\r\n <div class=\"flex-1 min-w-0\">\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Input formatted -->\r\n @case (fieldTypeEnum.InputFormatted) {\r\n <app-input-formatted\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-formatted>\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- T\u00E1ch ph\u1EA7n c\u1EA5u h\u00ECnh ra Component ri\u00EAng -->\r\n @if (mode() !== formModeEnum.View) {\r\n <div class=\"field-user-config-ctn\">\r\n <app-field-user-config\r\n [field]=\"field()\"\r\n [control]=\"control()\"\r\n ></app-field-user-config>\r\n </div>\r\n }\r\n </div>\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"] }]
4377
4674
  }] });
4378
4675
 
4379
4676
  const ZORRO_MODULES = [NzFormModule, NzGridModule, NzCollapseModule];
@@ -4559,11 +4856,11 @@ class DynamicFormComponent {
4559
4856
  return field.key || `field-${index}`;
4560
4857
  }
4561
4858
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormComponent, deps: [{ token: DynamicFormService }], target: i0.ɵɵFactoryTarget.Component }); }
4562
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFormComponent, isStandalone: true, selector: "app-dynamic-forms", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: false, isRequired: false, transformFunction: null }, valueChangesDebounceTime: { classPropertyName: "valueChangesDebounceTime", publicName: "valueChangesDebounceTime", isSignal: false, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, fieldGroups: { classPropertyName: "fieldGroups", publicName: "fieldGroups", isSignal: true, isRequired: false, transformFunction: null }, displayNonGroupedFields: { classPropertyName: "displayNonGroupedFields", publicName: "displayNonGroupedFields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formValueChanges: "formValueChanges", formSubmit: "formSubmit", formInvalid: "formInvalid" }, ngImport: i0, template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (groupField of groupedFields().grouped; track groupField.group.code) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "ngmodule", type: NzCollapseModule }, { kind: "component", type: i4$1.NzCollapsePanelComponent, selector: "nz-collapse-panel", inputs: ["nzActive", "nzDisabled", "nzShowArrow", "nzExtra", "nzHeader", "nzExpandedIcon"], outputs: ["nzActiveChange"], exportAs: ["nzCollapsePanel"] }, { kind: "component", type: i4$1.NzCollapseComponent, selector: "nz-collapse", inputs: ["nzAccordion", "nzBordered", "nzGhost", "nzExpandIconPosition"], exportAs: ["nzCollapse"] }, { kind: "component", type: DynamicFieldComponent, selector: "app-dynamic-field", inputs: ["field", "fields", "mode", "control", "formGroup", "formId"] }] }); }
4859
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFormComponent, isStandalone: true, selector: "app-dynamic-forms", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: false, isRequired: false, transformFunction: null }, valueChangesDebounceTime: { classPropertyName: "valueChangesDebounceTime", publicName: "valueChangesDebounceTime", isSignal: false, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, fieldGroups: { classPropertyName: "fieldGroups", publicName: "fieldGroups", isSignal: true, isRequired: false, transformFunction: null }, displayNonGroupedFields: { classPropertyName: "displayNonGroupedFields", publicName: "displayNonGroupedFields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formValueChanges: "formValueChanges", formSubmit: "formSubmit", formInvalid: "formInvalid" }, ngImport: i0, template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (\r\n groupField of groupedFields().grouped;\r\n track groupField.group.code\r\n ) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$4.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$4.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "ngmodule", type: NzCollapseModule }, { kind: "component", type: i4$3.NzCollapsePanelComponent, selector: "nz-collapse-panel", inputs: ["nzActive", "nzDisabled", "nzShowArrow", "nzExtra", "nzHeader", "nzExpandedIcon"], outputs: ["nzActiveChange"], exportAs: ["nzCollapsePanel"] }, { kind: "component", type: i4$3.NzCollapseComponent, selector: "nz-collapse", inputs: ["nzAccordion", "nzBordered", "nzGhost", "nzExpandIconPosition"], exportAs: ["nzCollapse"] }, { kind: "component", type: DynamicFieldComponent, selector: "app-dynamic-field", inputs: ["field", "fields", "mode", "control", "formGroup", "formId"] }] }); }
4563
4860
  }
4564
4861
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormComponent, decorators: [{
4565
4862
  type: Component,
4566
- args: [{ selector: 'app-dynamic-forms', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES, ...COMPONENTS], template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (groupField of groupedFields().grouped; track groupField.group.code) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"] }]
4863
+ args: [{ selector: 'app-dynamic-forms', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES, ...COMPONENTS], template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (\r\n groupField of groupedFields().grouped;\r\n track groupField.group.code\r\n ) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"] }]
4567
4864
  }], ctorParameters: () => [{ type: DynamicFormService }], propDecorators: { formId: [{
4568
4865
  type: Input
4569
4866
  }], valueChangesDebounceTime: [{
@@ -4637,5 +4934,5 @@ class RadioBaseField extends BaseField {
4637
4934
  * Generated bundle index. Do not edit.
4638
4935
  */
4639
4936
 
4640
- export { ALL_MIME_TYPES, ALL_MIME_TYPES_VALUE, APPLICATION_TYPES, AUDIO_TYPES, AvatarUploadField, BaseDateField, BaseField, BaseUploadField, CombineDependTypeEnum, CommonValidaterEnum, ControlTypeEnum, CustomValidaterEnum, DEFAULT_ACCEPTED_IMAGE_MIME_TYPES, DEFAULT_ACCEPTED_MIME_TYPES, DYNAMIC_FORM_CONFIG, DateFieldTypeEnum, DatePickerField, DatePickerOutputValueTypeEnum, DateRangeField, DateRangeOutputValueTypeEnum, DependTypeEnum, DynamicFormComponent, DynamicFormService, FieldGroup, FieldTypeEnum, FileService, FileSizeTypeEnum, FormConfigService, FormDataService, FormModeEnum, GroupedFields, IMAGE_TYPES, InputBaseField, InputFieldTypeEnum, InputPasswordField, InputTextField, MIME_TYPES_TO_EXTENSION, MultiFileUploadField, MultiImageUploadField, MultiSelectField, RadioBaseField, SelectBaseField, SelectFieldTypeEnum, SelectOutputValueTypeEnum, SingleFileUploadField, SingleSelectField, SuffixService, TEXT_TYPES, TableCellTypeEnum, TextareaField, TreeSelectField, UploadFieldTypeEnum, UploadOutputValueTypeEnum, VIDEO_TYPES, ViewModeEnum, videoTypesPath };
4937
+ export { ALL_MIME_TYPES, ALL_MIME_TYPES_VALUE, APPLICATION_TYPES, AUDIO_TYPES, AvatarUploadField, BaseDateField, BaseField, BaseUploadField, CombineDependTypeEnum, CommonValidaterEnum, ControlTypeEnum, CustomValidaterEnum, DEFAULT_ACCEPTED_IMAGE_MIME_TYPES, DEFAULT_ACCEPTED_MIME_TYPES, DYNAMIC_FORM_CONFIG, DateFieldTypeEnum, DatePickerField, DatePickerOutputValueTypeEnum, DateRangeField, DateRangeOutputValueTypeEnum, DependTypeEnum, DynamicFormComponent, DynamicFormService, FieldGroup, FieldTypeEnum, FileService, FileSizeTypeEnum, FormConfigService, FormDataService, FormModeEnum, GroupedFields, IMAGE_TYPES, InputBaseField, InputFieldTypeEnum, InputFormattedField, InputPasswordField, InputTextField, MIME_TYPES_TO_EXTENSION, MultiFileUploadField, MultiImageUploadField, MultiSelectField, NumericPreset, RadioBaseField, RegexTypeEnum, SelectBaseField, SelectFieldTypeEnum, SelectOutputValueTypeEnum, SingleFileUploadField, SingleSelectField, SuffixService, TEXT_TYPES, TableCellTypeEnum, TextareaField, UploadFieldTypeEnum, UploadOutputValueTypeEnum, VIDEO_TYPES, ViewModeEnum, videoTypesPath };
4641
4938
  //# sourceMappingURL=ldquocc-dynamic-form.mjs.map