@sd-angular/core 19.0.0-beta.94 → 19.0.0-beta.96

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 (194) hide show
  1. package/README.md +10 -10
  2. package/components/anchor/src/components/anchor/anchor.component.d.ts +4 -1
  3. package/components/anchor/src/components/anchor-item/anchor-item.component.d.ts +2 -1
  4. package/components/anchor/src/components/anchor-vertical/anchor-vertical-list.component.d.ts +3 -1
  5. package/components/badge/src/badge.component.d.ts +3 -4
  6. package/components/button/src/button.component.d.ts +2 -2
  7. package/components/document-builder/src/components/header-footer-builder/header-footer-builder.component.d.ts +2 -2
  8. package/components/document-builder/src/document-builder.model.d.ts +2 -2
  9. package/components/editor/src/models/editor.model.d.ts +2 -2
  10. package/components/editor/src/plugins/image-upload/utils/validate.utils.d.ts +2 -2
  11. package/components/form-generic/src/components/form-builder/components/attribute-table/attribute-table.component.d.ts +4 -2
  12. package/components/form-generic/src/components/form-builder/components/attribute-template/attribute-template.component.d.ts +2 -2
  13. package/components/form-generic/src/components/form-builder/components/configure-validation/configure-validation.component.d.ts +2 -2
  14. package/components/form-generic/src/components/form-builder/components/group/attribute/group-attribute.component.d.ts +23 -0
  15. package/components/form-generic/src/components/form-builder/components/group/index.d.ts +1 -0
  16. package/components/form-generic/src/components/form-builder/components/index.d.ts +1 -0
  17. package/components/form-generic/src/components/form-builder/components/table/control/table-control.component.d.ts +3 -0
  18. package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +61 -18
  19. package/components/form-generic/src/components/form-render/form-render.component.d.ts +2 -2
  20. package/components/form-generic/src/models/form-generic-component.model.d.ts +22 -2
  21. package/components/index.d.ts +1 -2
  22. package/components/modal/src/modal.component.d.ts +5 -3
  23. package/components/preview/index.d.ts +3 -0
  24. package/components/preview/src/preview-image/preview-image.component.d.ts +84 -24
  25. package/components/preview/src/preview-image/preview-image.types.d.ts +53 -0
  26. package/components/preview/src/preview-pdf/preview-pdf.component.d.ts +186 -0
  27. package/components/preview/src/preview-pdf/preview-pdf.types.d.ts +84 -0
  28. package/components/quick-action/src/quick-action.component.d.ts +5 -6
  29. package/components/section/src/section.component.d.ts +1 -2
  30. package/components/table/src/components/column-filter/column-filter.component.d.ts +3 -3
  31. package/components/table/src/table.component.d.ts +2 -2
  32. package/components/upload-file/src/upload-file.component.d.ts +4 -1
  33. package/configurations/src/sd-core.configuration.d.ts +2 -1
  34. package/fesm2022/sd-angular-core-components-anchor.mjs +19 -6
  35. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  36. package/fesm2022/sd-angular-core-components-button.mjs +1 -1
  37. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  38. package/fesm2022/sd-angular-core-components-code-editor.mjs +3 -3
  39. package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -1
  40. package/fesm2022/sd-angular-core-components-document-builder.mjs +3 -3
  41. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  42. package/fesm2022/sd-angular-core-components-editor.mjs +3 -3
  43. package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
  44. package/fesm2022/sd-angular-core-components-form-generic.mjs +627 -317
  45. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  46. package/fesm2022/sd-angular-core-components-history.mjs +3 -3
  47. package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
  48. package/fesm2022/sd-angular-core-components-import-excel.mjs +5 -5
  49. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  50. package/fesm2022/sd-angular-core-components-modal.mjs +8 -3
  51. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  52. package/fesm2022/sd-angular-core-components-preview.mjs +1963 -91
  53. package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
  54. package/fesm2022/sd-angular-core-components-quick-action.mjs +8 -19
  55. package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
  56. package/fesm2022/sd-angular-core-components-tab-router.mjs +31 -26
  57. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  58. package/fesm2022/sd-angular-core-components-table.mjs +32 -29
  59. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  60. package/fesm2022/sd-angular-core-components-upload-file.mjs +12 -8
  61. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  62. package/fesm2022/sd-angular-core-components.mjs +1 -2
  63. package/fesm2022/sd-angular-core-components.mjs.map +1 -1
  64. package/fesm2022/sd-angular-core-configurations.mjs.map +1 -1
  65. package/fesm2022/sd-angular-core-directives.mjs +2 -2
  66. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  67. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +2 -2
  68. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  69. package/fesm2022/sd-angular-core-forms-checkbox.mjs +6 -13
  70. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  71. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +6 -5
  72. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  73. package/fesm2022/sd-angular-core-forms-chip.mjs +6 -12
  74. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  75. package/fesm2022/sd-angular-core-forms-date-range.mjs +33 -26
  76. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  77. package/fesm2022/sd-angular-core-forms-date.mjs +29 -19
  78. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  79. package/fesm2022/sd-angular-core-forms-datetime.mjs +98 -57
  80. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  81. package/fesm2022/sd-angular-core-forms-input-number.mjs +2 -2
  82. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  83. package/fesm2022/sd-angular-core-forms-input.mjs +25 -10
  84. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  85. package/fesm2022/sd-angular-core-forms-radio.mjs +7 -15
  86. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  87. package/fesm2022/sd-angular-core-forms-select.mjs +4 -4
  88. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  89. package/fesm2022/sd-angular-core-forms-switch.mjs +6 -14
  90. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  91. package/fesm2022/sd-angular-core-forms-textarea.mjs +5 -5
  92. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  93. package/fesm2022/sd-angular-core-handlers.mjs +2 -2
  94. package/fesm2022/sd-angular-core-handlers.mjs.map +1 -1
  95. package/fesm2022/sd-angular-core-i18n.mjs +1610 -38
  96. package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
  97. package/fesm2022/sd-angular-core-interceptors.mjs +2 -2
  98. package/fesm2022/sd-angular-core-interceptors.mjs.map +1 -1
  99. package/fesm2022/sd-angular-core-models.mjs +6 -0
  100. package/fesm2022/sd-angular-core-models.mjs.map +1 -0
  101. package/fesm2022/sd-angular-core-modules-layout.mjs +87 -39
  102. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  103. package/fesm2022/sd-angular-core-services-confirm.mjs +2 -2
  104. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  105. package/fesm2022/sd-angular-core-services-docx.mjs +2 -2
  106. package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
  107. package/fesm2022/sd-angular-core-services-excel.mjs +27 -23
  108. package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
  109. package/fesm2022/sd-angular-core-services-notify.mjs +21 -21
  110. package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
  111. package/fesm2022/sd-angular-core-utilities-extensions.mjs +74 -830
  112. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  113. package/fesm2022/sd-angular-core-utilities-models.mjs +36 -136
  114. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  115. package/fesm2022/sd-angular-core.mjs +0 -1
  116. package/fesm2022/sd-angular-core.mjs.map +1 -1
  117. package/forms/autocomplete/src/autocomplete.component.d.ts +1 -2
  118. package/forms/checkbox/src/checkbox.component.d.ts +3 -3
  119. package/forms/chip/src/chip.component.d.ts +4 -4
  120. package/forms/chip-calendar/src/chip-calendar.component.d.ts +4 -4
  121. package/forms/date/src/date.component.d.ts +3 -5
  122. package/forms/date-range/src/date-range.component.d.ts +3 -5
  123. package/forms/datetime/src/datetime.component.d.ts +2 -2
  124. package/forms/datetime/src/popup/sd-datetime-picker.component.d.ts +10 -11
  125. package/forms/input/src/input.component.d.ts +1 -2
  126. package/forms/input-number/src/input-number.component.d.ts +1 -2
  127. package/forms/radio/src/radio.component.d.ts +3 -3
  128. package/forms/select/src/select.component.d.ts +5 -5
  129. package/forms/switch/src/switch.component.d.ts +3 -3
  130. package/forms/textarea/src/textarea.component.d.ts +1 -2
  131. package/i18n/index.d.ts +5 -5
  132. package/i18n/src/en.d.ts +406 -2
  133. package/i18n/src/i18n.messages.d.ts +2 -0
  134. package/i18n/src/i18n.pipe.d.ts +9 -0
  135. package/i18n/src/i18n.service.d.ts +14 -0
  136. package/i18n/src/i18n.token.d.ts +1 -0
  137. package/i18n/src/i18n.types.d.ts +31 -0
  138. package/i18n/src/ja.d.ts +2 -0
  139. package/i18n/src/ko.d.ts +2 -0
  140. package/i18n/src/vi.d.ts +2 -312
  141. package/i18n/src/zh.d.ts +2 -0
  142. package/models/index.d.ts +1 -0
  143. package/models/src/language.model.d.ts +2 -0
  144. package/modules/layout/modules/home/components/home-page/home-page.component.d.ts +25 -0
  145. package/package.json +82 -89
  146. package/public-api.d.ts +0 -1
  147. package/sd-angular-core-19.0.0-beta.96.tgz +0 -0
  148. package/services/notify/index.d.ts +1 -1
  149. package/services/notify/src/components/toast/toast.component.d.ts +3 -3
  150. package/services/notify/src/components/toast-container.component.d.ts +3 -4
  151. package/services/notify/src/notify.model.d.ts +1 -1
  152. package/services/notify/src/notify.service.d.ts +5 -5
  153. package/utilities/extensions/src/array.extension.d.ts +1 -7
  154. package/utilities/extensions/src/color.extension.d.ts +5 -20
  155. package/utilities/extensions/src/date.extension.d.ts +1 -18
  156. package/utilities/extensions/src/number.extension.d.ts +1 -10
  157. package/utilities/extensions/src/string.extension.d.ts +30 -4
  158. package/utilities/extensions/src/utility.extension.d.ts +6 -0
  159. package/utilities/models/src/color.model.d.ts +4 -1
  160. package/utilities/models/src/empty.model.d.ts +3 -0
  161. package/utilities/models/src/filter.model.d.ts +12 -24
  162. package/utilities/models/src/maybe-async.model.d.ts +10 -4
  163. package/utilities/models/src/nested-key-of.model.d.ts +4 -5
  164. package/utilities/models/src/operator.model.d.ts +9 -4
  165. package/utilities/models/src/order.model.d.ts +4 -5
  166. package/utilities/models/src/paging.model.d.ts +8 -16
  167. package/utilities/models/src/pattern.model.d.ts +13 -0
  168. package/utilities/models/src/size.model.d.ts +4 -1
  169. package/components/anchor-v2/index.d.ts +0 -1
  170. package/components/anchor-v2/src/components/anchor-item-v2/anchor-item-v2.component.d.ts +0 -12
  171. package/components/anchor-v2/src/components/anchor-v2/anchor-v2.component.d.ts +0 -19
  172. package/components/anchor-v2/src/components/anchor-vertical-v2/anchor-vertical-list-v2.component.d.ts +0 -16
  173. package/components/anchor-v2/src/components/index.d.ts +0 -2
  174. package/components/anchor-v2/src/index.d.ts +0 -2
  175. package/components/anchor-v2/src/models/index.d.ts +0 -1
  176. package/components/anchor-v2/src/models/sd-anchor-v2.model.d.ts +0 -7
  177. package/components/workflow/index.d.ts +0 -2
  178. package/components/workflow/src/configurations/index.d.ts +0 -1
  179. package/components/workflow/src/configurations/workflow.configuration.d.ts +0 -10
  180. package/fesm2022/sd-angular-core-components-anchor-v2.mjs +0 -151
  181. package/fesm2022/sd-angular-core-components-anchor-v2.mjs.map +0 -1
  182. package/fesm2022/sd-angular-core-components-workflow.mjs +0 -17
  183. package/fesm2022/sd-angular-core-components-workflow.mjs.map +0 -1
  184. package/fesm2022/sd-angular-core-services-firebase.mjs +0 -38
  185. package/fesm2022/sd-angular-core-services-firebase.mjs.map +0 -1
  186. package/i18n/src/sd-i18n.messages.d.ts +0 -2
  187. package/i18n/src/sd-i18n.pipe.d.ts +0 -9
  188. package/i18n/src/sd-i18n.service.d.ts +0 -12
  189. package/i18n/src/sd-i18n.token.d.ts +0 -1
  190. package/i18n/src/sd-i18n.types.d.ts +0 -5
  191. package/sd-angular-core-19.0.0-beta.94.tgz +0 -0
  192. package/services/firebase/index.d.ts +0 -2
  193. package/services/firebase/src/firebase.model.d.ts +0 -8
  194. package/services/firebase/src/firebase.service.d.ts +0 -14
@@ -1,764 +1,63 @@
1
- /* eslint-disable no-useless-escape */
1
+ import { ValidationUtilities, StringUtilities as StringUtilities$1, ColorUtilities } from '@sdcorejs/utils/fns';
2
+ export { ArrayUtilities, BrowserUtilities, ColorUtilities, DateUtilities, NumberUtilities, Utilities, ValidationUtilities } from '@sdcorejs/utils/fns';
3
+
2
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
- // declare global {
4
- // interface StringConstructor {
5
- // isValidEmail(value: any): boolean;
6
- // isValidPhone(value: any): boolean;
7
- // isValidCode(value: any): boolean;
8
- // changeAliasLowerCase(alias: any): string;
9
- // aliasIncludes(alias: any, searchText: any): boolean;
10
- // format(template: string, ...arr: any[]): string;
11
- // templateToDisplay(template: string, entity: any): string; // Convert theo format ${key}
12
- // encrypt(obj: any): string;
13
- // decrypt(encripted: string): any;
14
- // isNullOrEmpty(value: any): boolean;
15
- // isNullOrWhiteSpace(value: any): boolean;
16
- // }
17
- // }
18
- const REGEX_EMAIL = '^(([^<>()[\\].,;:\\s@"]+(\\.[^<>()[\\].,;:\\s@"]+)*)|(".+"))@(([^<>()[\\].,;:\\s@"]+\\.)+[^<>()[\\].,;:\\s@"]{2,})$';
19
- const REGEX_PHONE = '^[+]*[(]{0,1}[+]?[0-9]{1,4}[)]{0,1}[-\\s./0-9]*$';
20
- const REGEX_PHONE_VN = '^(?:\\+84|0|84)(3[2-9]|5[2689]|7[06-9]|8[1-689]|9[0-9])\\d{7}$';
21
- const REGEX_IDVN = '^\\d{12}$';
22
- const REGEX_PASSPORT = '^[A-Z]\\d{7}$';
23
- const REGEX_IDVN_OR_PASSPORT = '^(\\d{12}|[A-Z]\\d{7})$';
24
- const REGEX_TIME = '^(?:[01]\\d|2[0-3]):[0-5]\\d$';
25
- const isValidEmail = (value) => {
26
- if (!value) {
27
- return false;
28
- }
29
- const re = new RegExp(REGEX_EMAIL);
30
- return re.test(value);
31
- };
32
- const isValidPhone = (value) => {
33
- if (!value) {
34
- return false;
35
- }
36
- const re = new RegExp(REGEX_PHONE);
37
- return re.test(value);
38
- };
39
- const isValidCode = (value) => {
40
- if (!value) {
41
- return false;
42
- }
43
- const re = /^[a-zA-Z0-9\@\_\-]{2,20}$/;
44
- return re.test(value);
45
- };
46
- const isNullOrEmpty = (value) => {
47
- return value === undefined || value === null || value === '';
48
- };
49
- const isNullOrWhiteSpace = (value) => {
50
- return value === undefined || value === null || typeof value !== 'string' || value.match(/^\s*$/) !== null;
51
- };
52
- const changeAliasLowerCase$1 = (alias) => {
53
- let str = alias?.toString() ?? '';
54
- str = str.toString().toLowerCase();
55
- str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
56
- str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e');
57
- str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
58
- str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
59
- str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
60
- str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
61
- str = str.replace(/đ/g, 'd');
62
- str = str.replace(/!|@|%|\^|\*|\(|\)|\+|\=|\<|\>|\?|\/|,|\.|\:|\;|\'|\"|\&|\#|\[|\]|~|\$|_|`|-|{|}|\||\\/g, ' ');
63
- str = str.replace(/ + /g, ' ');
64
- str = str.trim();
65
- return str;
66
- };
67
- const aliasIncludes = (alias, searchText) => {
68
- return changeAliasLowerCase$1(alias).includes(changeAliasLowerCase$1(searchText));
69
- };
70
- const format = (template, ...arr) => {
71
- for (let i = 0; i < arr.length; i++) {
72
- const regexp = new RegExp(`\\{${i}\\}`, 'gi');
73
- template = template.replace(regexp, arr[i]);
74
- }
75
- return template;
76
- };
77
- const getValueByPath = (entity, key) => {
78
- let value = entity;
79
- for (const part of key.split('.')) {
80
- value = value?.[part];
81
- }
82
- return value;
83
- };
84
- // Convert: `[${code}] ${name}` => `[Mã] Tên`;
85
- const templateToDisplay = (template, entity) => {
86
- if (!template) {
87
- return template;
88
- }
89
- const regex = /\$\{([A-Za-z0-9._-]*)\}/g;
90
- const matches = template.match(regex) || [];
91
- for (const match of matches) {
92
- const key = match.slice(2, match.length - 1);
93
- if (key) {
94
- template = template.replace(match, getValueByPath(entity, key) ?? '');
95
- }
96
- }
97
- return template;
98
- };
99
- const EXACT_TEMPLATE_REGEX = /^\$\{([A-Za-z0-9._-]*)\}$/;
100
- const NUMBER_LITERAL_REGEX = /^-?\d+(\.\d+)?$/;
101
- // Convert placeholder/literal an toàn, không thực thi JavaScript động.
102
- const parseExpression = (template, entity) => {
103
- if (!template) {
104
- return undefined;
105
- }
106
- const trimmedTemplate = template.trim();
107
- const exactTemplateMatch = trimmedTemplate.match(EXACT_TEMPLATE_REGEX);
108
- if (exactTemplateMatch?.[1]) {
109
- return getValueByPath(entity, exactTemplateMatch[1]);
110
- }
111
- if (trimmedTemplate === 'true') {
112
- return true;
113
- }
114
- if (trimmedTemplate === 'false') {
115
- return false;
116
- }
117
- if (trimmedTemplate === 'null') {
118
- return null;
119
- }
120
- if (trimmedTemplate === 'undefined') {
121
- return undefined;
122
- }
123
- if (NUMBER_LITERAL_REGEX.test(trimmedTemplate)) {
124
- return Number(trimmedTemplate);
125
- }
126
- return templateToDisplay(template, entity);
127
- };
128
- const SALT = 'cb9f4b2a-d26c-4787-a66e-e7130ee00f95';
129
- const encrypt = (obj) => {
130
- obj = JSON.stringify(obj).split('');
131
- for (let i = 0, l = obj.length; i < l; i++)
132
- if (obj[i] == '{')
133
- obj[i] = '}';
134
- else if (obj[i] == '}')
135
- obj[i] = '{';
136
- return encodeURI(SALT + obj.join(''));
137
- };
138
- const decrypt = (encripted) => {
139
- encripted = decodeURI(encripted);
140
- if (SALT && encripted.indexOf(SALT) != 0)
141
- throw new Error('object cannot be decrypted');
142
- const strs = encripted.substring(SALT.length).split('');
143
- for (let i = 0, l = strs.length; i < l; i++)
144
- if (strs[i] == '{')
145
- strs[i] = '}';
146
- else if (strs[i] == '}')
147
- strs[i] = '{';
148
- return JSON.parse(strs.join(''));
149
- };
150
- // Conver "Đội Kỹ Thuật" thành "doi_ky_thuat"
151
- const convertToSnakeCaseCode = (name) => {
152
- if (typeof name !== 'string') {
153
- throw new Error('Invalid name');
154
- }
155
- return name
156
- .normalize('NFD') // Tách dấu
157
- .replace(/[\u0300-\u036f]/g, '') // Xóa các dấu vừa tách
158
- .replace(/[đĐ]/g, 'd') // Quan trọng: Xử lý chữ đ/Đ thành d
159
- .trim()
160
- .replace(/[^a-zA-Z0-9]+/g, '_') // Thay ký tự đặc biệt bằng _
161
- .replace(/^_+|_+$/g, '') // Xóa _ ở đầu/cuối
162
- .toLowerCase(); // Chuyển thành chữ thường: doi_ky_thuat
163
- };
164
- /**
165
- * Tạo code duy nhất, tự động thêm suffix _n nếu bị trùng
166
- * @param name Tên cần convert
167
- * @param existingCodes Danh sách các code đã tồn tại
168
- */
169
- const generateUniqueCode = (name, existingCodes) => {
170
- // 1. Tạo base code trước
171
- const baseCode = convertToSnakeCaseCode(name);
172
- // 2. Nếu code chưa tồn tại thì trả về luôn
173
- if (!existingCodes.includes(baseCode)) {
174
- return baseCode;
175
- }
176
- // 3. Nếu trùng, bắt đầu loop để tìm index phù hợp
177
- let index = 1;
178
- let newCode = `${baseCode}_${index}`;
179
- while (existingCodes.includes(newCode)) {
180
- index++;
181
- newCode = `${baseCode}_${index}`;
182
- }
183
- return newCode;
184
- };
185
- const sha256 = async (input) => {
186
- const buffer = new TextEncoder().encode(input);
187
- const hash = await crypto.subtle.digest('SHA-256', buffer);
188
- const bytes = new Uint8Array(hash);
189
- let binary = '';
190
- for (let i = 0; i < bytes.length; i++)
191
- binary += String.fromCharCode(bytes[i]);
192
- return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
193
- };
5
+ // Deprecated wrappers — moved to ValidationUtilities
6
+ const isValidEmail = (value) => ValidationUtilities.isEmail(value);
7
+ const isValidPhone = (value) => ValidationUtilities.isPhone(value);
8
+ const isValidCode = (value) => ValidationUtilities.isCode(value);
194
9
  const StringUtilities = {
195
- REGEX_EMAIL,
196
- REGEX_PHONE,
197
- REGEX_PHONE_VN,
198
- REGEX_IDVN,
199
- REGEX_PASSPORT,
200
- REGEX_IDVN_OR_PASSPORT,
201
- REGEX_TIME,
10
+ ...StringUtilities$1,
11
+ // Deprecated aliases for renamed regex constants (renamed in @sdcorejs/utils v1.0.2)
12
+ /** @deprecated Use REGEX_VN_PHONE instead */
13
+ REGEX_PHONE_VN: StringUtilities$1.REGEX_VN_PHONE,
14
+ /** @deprecated Use REGEX_VN_ID instead */
15
+ REGEX_IDVN: StringUtilities$1.REGEX_VN_ID,
16
+ /** @deprecated Use REGEX_VN_ID_OR_PASSPORT instead */
17
+ REGEX_IDVN_OR_PASSPORT: StringUtilities$1.REGEX_VN_ID_OR_PASSPORT,
18
+ // Deprecated validators — moved to ValidationUtilities
19
+ /** @deprecated Use {@link ValidationUtilities.isEmail} instead */
202
20
  isValidEmail,
21
+ /** @deprecated Use {@link ValidationUtilities.isPhone} instead */
203
22
  isValidPhone,
23
+ /** @deprecated Use {@link ValidationUtilities.isCode} instead */
204
24
  isValidCode,
205
- changeAliasLowerCase: changeAliasLowerCase$1,
206
- aliasIncludes,
207
- format,
208
- templateToDisplay,
209
- parseExpression,
210
- encrypt,
211
- decrypt,
212
- isNullOrEmpty,
213
- isNullOrWhiteSpace,
214
- convertToSnakeCaseCode,
215
- generateUniqueCode,
216
- sha256,
217
25
  };
218
26
 
219
- /* eslint-disable @typescript-eslint/no-explicit-any */
220
- // export {};
221
- // declare global {
222
- // interface Array<T = any> {
223
- // search(searchText: string | number, fields?: (string | undefined) | (string | undefined)[], children?: string): T[];
224
- // paging(pageSize: number, page?: number): T[];
225
- // toObject(key: string): Record<string, T>;
226
- // union(key: string, ...args: T[][]): T[];
227
- // distinct(): T[];
228
- // }
229
- // interface ArrayConstructor {
230
- // search<T = any>(items: T[], searchText: string | number, fields?: (string | undefined) | (string | undefined)[]): T[];
231
- // union<T = any>(key: string, ...args: T[][]): T[];
232
- // toObject<T = any>(key: string, items: T[]): Record<string, T>;
233
- // }
234
- // }
235
- const search = (items, searchText, fields, children) => {
236
- if (!searchText?.toString()) {
237
- return items;
238
- }
239
- if (!items?.length) {
240
- return items;
241
- }
242
- if (Array.isArray(fields)) {
243
- const fs = fields.filter(e => e !== undefined && e !== null && e !== '');
244
- if (!fs.length) {
245
- return items.filter(item => item !== undefined && item !== null && StringUtilities.aliasIncludes(item, searchText));
246
- }
247
- return items.filter(item => (item !== undefined && item !== null && fs.some(field => StringUtilities.aliasIncludes(item[field], searchText))) ||
248
- (children && search(item[children], searchText, fields, children)?.length));
249
- }
250
- if (!fields) {
251
- return items.filter(item => item !== undefined && item !== null && StringUtilities.aliasIncludes(item, searchText));
252
- }
253
- return items.filter(item => (item !== undefined && item !== null && StringUtilities.aliasIncludes(item[fields], searchText)) ||
254
- (children && search(item[children], searchText, fields, children)?.length));
255
- };
256
- // Array.prototype.search = function <T = any>(
257
- // searchText: any,
258
- // fields?: (string | undefined) | (string | undefined)[],
259
- // children?: string
260
- // ): T[] {
261
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
262
- // const items: T[] = this;
263
- // return search(items, searchText, fields, children);
264
- // };
265
- // Array.prototype.paging = function <T extends Record<string, any> = any>(pageSize: number, page = 0): T[] {
266
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
267
- // const items: T[] = this;
268
- // return items.filter((_, index) => {
269
- // return index >= page * pageSize && index < (page + 1) * pageSize;
270
- // });
271
- // };
272
- // Array.prototype.toObject = function <T = any>(key: string): Record<string, T> {
273
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
274
- // const items: T[] = this;
275
- // return items
276
- // .filter(item => item !== undefined && item !== null)
277
- // .reduce<Record<string, any>>((obj, item: Record<string, any>) => {
278
- // const objKey = item?.[key]?.toString();
279
- // if (typeof objKey === 'string') {
280
- // obj[objKey] = item;
281
- // }
282
- // return obj;
283
- // }, {});
284
- // };
285
- // Array.prototype.union = function <T = Record<string, any>>(key: string, ...args: T[][]) {
286
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
287
- // let results: T[] = this;
288
- // const filterUnion = <T = any>(val: T, index: number, self: T[]) =>
289
- // val !== undefined && val !== null && self.findIndex(e => (e as any)[key] === (val as any)[key]) === index;
290
- // if (!args?.length) {
291
- // return results.filter(filterUnion);
292
- // }
293
- // for (const arg of args) {
294
- // if (Array.isArray(arg)) {
295
- // results = [...results, ...arg].filter(filterUnion);
296
- // }
297
- // }
298
- // return results;
299
- // };
300
- // Array.prototype.distinct = function () {
301
- // if (Array.isArray(this)) {
302
- // return [...new Set(this)];
303
- // }
304
- // return this;
305
- // };
306
- const union = (key, ...args) => {
307
- let results = [];
308
- const filterUnion = (val, index, self) => val !== undefined && val !== null && self.findIndex(e => e[key] === val[key]) === index;
309
- if (!args?.length) {
310
- return [];
311
- }
312
- for (const arg of args) {
313
- if (Array.isArray(arg)) {
314
- results = [...results, ...arg].filter(filterUnion);
315
- }
316
- }
317
- return results;
318
- };
319
- const toObject = (key, items) => {
320
- if (!Array.isArray(items)) {
321
- return {};
322
- }
323
- return items
324
- .filter(item => item !== undefined && item !== null)
325
- .reduce((obj, item) => {
326
- const objKey = item?.[key]?.toString();
327
- if (typeof objKey === 'string') {
328
- obj[objKey] = item;
329
- }
330
- return obj;
331
- }, {});
332
- };
333
- const distinct = (items) => {
334
- if (Array.isArray(items)) {
335
- return [...new Set(items)];
336
- }
337
- return [];
338
- };
339
- const paging = (items, pageSize, page = 0) => {
340
- if (!Array.isArray(items)) {
341
- return [];
342
- }
343
- return items.filter((_, index) => {
344
- return index >= page * pageSize && index < (page + 1) * pageSize;
345
- });
346
- };
347
- const ArrayUtilities = {
348
- search,
349
- union,
350
- toObject,
351
- distinct,
352
- paging,
353
- };
354
-
355
- /* eslint-disable @typescript-eslint/no-explicit-any */
356
- // export {};
357
- // declare global {
358
- // interface NumberConstructor {
359
- // toVNCurrency(value: any): string | null;
360
- // toISO(value: any): string | null;
361
- // isNumber(value: any): boolean;
362
- // isPositiveInteger(value: any): boolean;
363
- // isPositiveNumber(value: any): boolean;
364
- // round(value: any, digits?: number): number | null;
365
- // }
366
- // }
367
- const toVNCurrency = (value) => {
368
- value = (value ?? '').toString().replace(/,/g, '');
369
- if (!value) {
370
- return null;
371
- }
372
- const val = +value;
373
- if (!Number.isNaN(val)) {
374
- return val.toLocaleString('vi-VN', { maximumFractionDigits: 10 });
375
- }
376
- return null;
377
- };
378
- const toVN = (value) => {
379
- value = (value ?? '').toString().replace(/,/g, '');
380
- if (!value) {
381
- return null;
382
- }
383
- const val = +value;
384
- if (!Number.isNaN(val)) {
385
- return val.toLocaleString('vi-VN', { maximumFractionDigits: 10 });
386
- }
387
- return null;
388
- };
389
- const toISO = (value) => {
390
- value = (value ?? '').toString().replace(/,/g, '');
391
- if (!value) {
392
- return null;
393
- }
394
- const val = +value;
395
- if (!Number.isNaN(val)) {
396
- return val.toLocaleString('en-US', { maximumFractionDigits: 10 });
397
- }
398
- return null;
399
- };
400
- const isPositiveInteger = (value) => {
401
- if (!value) {
402
- return false;
403
- }
404
- const regex = /^([0-9]*)$/;
405
- return regex.test(value) && +value > 0;
406
- };
407
- const isPositiveNumber = (value) => {
408
- if (!value) {
409
- return false;
410
- }
411
- const regex = /^([0-9]*)(\.[0-9]+$){0,1}$/;
412
- return regex.test(value) && +value > 0;
413
- };
414
- const isNumber = (value) => {
415
- if (value === undefined || value === null || value === '') {
416
- return false;
417
- }
418
- return !Number.isNaN(+value);
419
- };
420
- const round = (value, digits) => {
421
- digits = digits ?? 2;
422
- if (!NumberUtilities.isNumber(value)) {
423
- return null;
424
- }
425
- const val = Math.pow(10, digits);
426
- return Math.round(value * val) / val;
427
- };
428
- const NumberUtilities = {
429
- toVNCurrency,
430
- toVN,
431
- toISO,
432
- isPositiveInteger,
433
- isPositiveNumber,
434
- isNumber,
435
- round,
436
- };
437
-
438
- /* eslint-disable @typescript-eslint/no-explicit-any */
439
- // export {};
440
- // declare global {
441
- // interface Date {
442
- // begin(): Date;
443
- // end(): Date;
444
- // addMiliseconds(miliseconds: number): Date;
445
- // addHours(hours: number): Date;
446
- // addDays(days: number): Date;
447
- // addMonths(months: number): Date;
448
- // toFormat(format: string): string;
449
- // }
450
- // interface DateConstructor {
451
- // equal(date1: any, date2: any): boolean;
452
- // dayDiff(date1: any, date2: any): number | null;
453
- // monthDiff(date1: any, date2: any): number | null;
454
- // yearDiff(date1: any, date2: any): number | null;
455
- // age(date1: any, date2: any): number | null;
456
- // parseFrom(value: any, format: string): Date | null;
457
- // isDate(value: any): boolean;
458
- // toFormat(value: any, format: string): string;
459
- // addMiliseconds(value: any, miliseconds: number): Date | null;
460
- // addHours(value: any, hours: number): Date | null;
461
- // addDays(value: any, days: number): Date | null;
462
- // addMonths(value: any, months: number): Date | null;
463
- // begin(value: any): Date | null;
464
- // end(value: any): Date | null;
465
- // timeDifference(previous: any, current?: any): string;
466
- // }
467
- // }
468
- const isDate = (value) => {
469
- if (value === undefined || value === null || value === '') {
470
- return false;
471
- }
472
- if (typeof value === 'string') {
473
- // Kiểm tra regex
474
- if (value.length < 8) {
475
- return false;
476
- }
477
- else {
478
- const date1 = value.substring(0, 10); // yyyy/MM/dd
479
- const date2 = value.substring(0, 9); // yyyy/M/dd, yyyy/MM/d
480
- const date3 = value.substring(0, 8); // yyyy/M/d
481
- const regex1 = /^(0[1-9]|[1-9]|1[0-2])(-|\/)(0[1-9]|[1-9]|[12][0-9]|3[01])(-|\/)(\d{4})$/; // MM/dd/yyyy, MM-dd-yyyy
482
- const regex2 = /^(\d{4})(-|\/)(0[1-9]|[1-9]|1[0-2])(-|\/)(0[1-9]|[1-9]|[12][0-9]|3[01])$/; // yyyy/MM/dd, yyyy-MM-dd
483
- const result = regex1.test(date1) || regex2.test(date1) || regex1.test(date2) || regex2.test(date2) || regex1.test(date3) || regex2.test(date3);
484
- if (result) {
485
- const date = new Date(value);
486
- return !isNaN(date.getTime());
487
- }
488
- else {
489
- return false;
490
- }
491
- }
492
- }
493
- else {
494
- const date = new Date(value);
495
- return !isNaN(date.getTime());
496
- }
497
- };
498
- // const toFormatBackup = (value: any, format: string): string => {
499
- // if (!isDate(value)) {
500
- // return '';
501
- // }
502
- // const date: Date = new Date(value);
503
- // let result = format;
504
- // result = result.replace('yyyy', date.getFullYear().toString());
505
- // result = result.replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'));
506
- // result = result.replace('dd', date.getDate().toString().padStart(2, '0'));
507
- // result = result.replace('HH', date.getHours().toString().padStart(2, '0'));
508
- // result = result.replace('mm', date.getMinutes().toString().padStart(2, '0'));
509
- // result = result.replace('ss', date.getSeconds().toString().padStart(2, '0'));
510
- // return result;
511
- // };
512
- const toFormat = (value, format) => {
513
- if (!isDate(value)) {
514
- return '';
515
- }
516
- const isDatePart = (type) => {
517
- return type === 'year' || type === 'month' || type === 'day' || type === 'hour' || type === 'minute' || type === 'second';
518
- };
519
- const date = new Date(value);
520
- const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
521
- const parts = new Intl.DateTimeFormat('en-GB', {
522
- timeZone,
523
- year: 'numeric',
524
- month: '2-digit',
525
- day: '2-digit',
526
- hour: '2-digit',
527
- minute: '2-digit',
528
- second: '2-digit',
529
- hour12: false,
530
- }).formatToParts(date);
531
- const map = {};
532
- parts.forEach(part => {
533
- if (isDatePart(part.type)) {
534
- map[part.type] = part.value;
535
- }
536
- });
537
- return format
538
- .replace('yyyy', map.year ?? '')
539
- .replace('MM', map.month ?? '')
540
- .replace('dd', map.day ?? '')
541
- .replace('HH', map.hour ?? '')
542
- .replace('mm', map.minute ?? '')
543
- .replace('ss', map.second ?? '');
544
- };
545
- const addDays = (value, days) => {
546
- if (!isDate(value)) {
547
- return null;
548
- }
549
- const date = new Date(value);
550
- date.setDate(date.getDate() + days);
551
- return date;
552
- };
553
- const addMiliseconds = (value, miliseconds) => {
554
- if (!isDate(value)) {
555
- return null;
556
- }
557
- const date = new Date(value);
558
- date.setMilliseconds(date.getMilliseconds() + miliseconds);
559
- return date;
560
- };
561
- const begin = (value) => {
562
- if (!isDate(value)) {
563
- return null;
564
- }
565
- return new Date(toFormat(value, 'MM/dd/yyyy'));
566
- };
567
- const end = (value) => {
568
- if (!isDate(value)) {
569
- return null;
570
- }
571
- return addMiliseconds(begin(addDays(value, 1)), -1);
572
- };
573
- const equal = (date1, date2) => {
574
- if (!isDate(date1) && !isDate(date2)) {
575
- return true;
576
- }
577
- if (!isDate(date1) || !isDate(date2)) {
578
- return false;
579
- }
580
- return new Date(date1).getTime() === new Date(date2).getTime();
581
- };
582
- const dayDiff = (date1, date2) => {
583
- if (!isDate(date1) || !isDate(date2)) {
584
- return null;
585
- }
586
- const diff = new Date(date2).getTime() - new Date(date1).getTime();
587
- return Math.floor(diff / (24 * 3600 * 1000));
588
- };
589
- const monthDiff = (date1, date2) => {
590
- if (!isDate(date1) || !isDate(date2)) {
591
- return null;
592
- }
593
- const d1Y = date1.getFullYear();
594
- const d2Y = date2.getFullYear();
595
- const d1M = date2.getMonth();
596
- const d2M = date2.getMonth();
597
- return d2M + 12 * d2Y - (d1M + 12 * d1Y);
598
- };
599
- const yearDiff = (date1, date2) => {
600
- if (!isDate(date1) || !isDate(date2)) {
601
- return null;
602
- }
603
- return new Date(date2).getFullYear() - new Date(date1).getFullYear();
604
- };
605
- const age = (date1, date2) => {
606
- const diff = monthDiff(date1, date2);
607
- if (!diff) {
608
- return null;
609
- }
610
- return NumberUtilities.round(diff / 12);
611
- };
612
- const parseFrom = (value, format) => {
613
- if (!value) {
614
- return null;
615
- }
616
- if (!format) {
617
- return null;
618
- }
619
- value = value.toString();
620
- const dmy = format.indexOf('dd') > -1 && format.indexOf('MM') > -1 && format.indexOf('yyyy') > -1;
621
- const hms = format.indexOf('HH') > -1 || format.indexOf('mm') > -1 || format.indexOf('ss') > -1;
622
- let strDate = '';
623
- if (dmy) {
624
- const dd = value.substr(format.indexOf('dd'), 'dd'.length);
625
- const MM = value.substr(format.indexOf('MM'), 'MM'.length);
626
- const yyyy = value.substr(format.indexOf('yyyy'), 'yyyy'.length);
627
- if (+yyyy > 0 && +MM > 0 && +dd > 0) {
628
- strDate += `${MM}/${dd}/${yyyy}`;
629
- }
630
- else {
631
- return null;
632
- }
633
- }
634
- else {
635
- const today = new Date();
636
- const yyyy = today.getFullYear().toString();
637
- const MM = (today.getMonth() + 1).toString().padStart(2, '0');
638
- const dd = today.getDate().toString().padStart(2, '0');
639
- strDate += `${MM}/${dd}/${yyyy}`;
640
- }
641
- if (hms) {
642
- const HH = format.indexOf('HH') > -1 ? value.substr(format.indexOf('HH'), 'HH'.length) : '00';
643
- const mm = format.indexOf('mm') > -1 ? value.substr(format.indexOf('mm'), 'mm'.length) : '00';
644
- const ss = format.indexOf('ss') > -1 ? value.substr(format.indexOf('ss'), 'ss'.length) : '00';
645
- strDate += ` ${HH || '00'}:${mm || '00'}:${ss || '00'}`;
646
- }
647
- if (!isDate(strDate)) {
648
- return null;
649
- }
650
- return new Date(strDate);
651
- };
652
- const addHours = (value, hours) => {
653
- if (!isDate(value)) {
654
- return null;
655
- }
656
- const date = new Date(value);
657
- date.setHours(date.getHours() + hours);
658
- return date;
659
- };
660
- const addMonths = (value, months) => {
661
- if (!isDate(value)) {
662
- return null;
663
- }
664
- const date = new Date(value);
665
- date.setMonth(date.getMonth() + months);
666
- return date;
667
- };
668
- const timeDifference = function (previous, current = new Date()) {
669
- if (!isDate(previous)) {
670
- return '';
671
- }
672
- if (!isDate(current)) {
673
- return '';
674
- }
675
- const from = new Date(previous);
676
- const to = new Date(current);
677
- const msPerMinute = 60 * 1000;
678
- const msPerHour = msPerMinute * 60;
679
- const msPerDay = msPerHour * 24;
680
- const msPerMonth = msPerDay * 30;
681
- const msPerYear = msPerDay * 365;
682
- const elapsed = to.getTime() - from.getTime();
683
- if (elapsed < msPerMinute) {
684
- return `${Math.round(elapsed / 1000)} seconds ago`;
685
- }
686
- if (elapsed < msPerHour) {
687
- return `${Math.round(elapsed / msPerMinute)} minutes ago`;
688
- }
689
- if (elapsed < msPerDay) {
690
- return `${Math.round(elapsed / msPerHour)} hours ago`;
691
- }
692
- if (elapsed < msPerMonth) {
693
- return `${Math.round(elapsed / msPerDay)} days ago`;
694
- }
695
- if (elapsed < msPerYear) {
696
- return `${Math.round(elapsed / msPerMonth)} months ago`;
27
+ // Hardcoded i18n cho upload utility — pure function nên không inject() được I18nService.
28
+ // Đọc lang trực tiếp từ localStorage (cùng key với I18nService: 'sd-core.language').
29
+ // Khi I18nService chuyển sang reload-on-change model, lang ở đây luôn đồng bộ với UI.
30
+ const SD_UPLOAD_MESSAGES = {
31
+ vi: {
32
+ 'invalid-format': '[{name}] File tải lên không đúng định dạng. Vui lòng chọn lại',
33
+ 'invalid-size': '[{name}] Kích thước file không hợp lệ. Vui lòng chọn một file khác',
34
+ },
35
+ en: {
36
+ 'invalid-format': '[{name}] Invalid file format. Please select again',
37
+ 'invalid-size': '[{name}] Invalid file size. Please choose a different file',
38
+ },
39
+ ja: {
40
+ 'invalid-format': '[{name}] ファイル形式が正しくありません。もう一度選択してください',
41
+ 'invalid-size': '[{name}] ファイルサイズが正しくありません。別のファイルを選択してください',
42
+ },
43
+ ko: {
44
+ 'invalid-format': '[{name}] 파일 형식이 올바르지 않습니다. 다시 선택해 주세요',
45
+ 'invalid-size': '[{name}] 파일 크기가 올바르지 않습니다. 다른 파일을 선택해 주세요',
46
+ },
47
+ zh: {
48
+ 'invalid-format': '[{name}] 文件格式不正确,请重新选择',
49
+ 'invalid-size': '[{name}] 文件大小不符合要求,请选择其他文件',
50
+ },
51
+ };
52
+ const getSdUploadLang = () => {
53
+ try {
54
+ const stored = localStorage.getItem('sd-core.language');
55
+ if (stored && stored in SD_UPLOAD_MESSAGES)
56
+ return stored;
697
57
  }
698
- return `${Math.round(elapsed / msPerYear)} years ago`;
699
- };
700
- // Date.prototype.begin = function () {
701
- // return new Date(this.toFormat('MM/dd/yyyy'));
702
- // };
703
- // Date.prototype.end = function () {
704
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
705
- // const date: Date = this;
706
- // return date.addDays(1).begin().addMiliseconds(-1);
707
- // };
708
- // Date.prototype.addMiliseconds = function (miliseconds: number) {
709
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
710
- // const date: Date = this;
711
- // date.setMilliseconds(date.getMilliseconds() + miliseconds);
712
- // return date;
713
- // };
714
- // Date.prototype.addHours = function (hours: number) {
715
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
716
- // const date: Date = this;
717
- // date.setHours(date.getHours() + hours);
718
- // return date;
719
- // };
720
- // Date.prototype.addDays = function (days: number) {
721
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
722
- // const date: Date = this;
723
- // date.setDate(date.getDate() + days);
724
- // return date;
725
- // };
726
- // Date.prototype.addMonths = function (months: number) {
727
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
728
- // const date: Date = this;
729
- // date.setMonth(date.getMonth() + months);
730
- // return date;
731
- // };
732
- // Date.prototype.toFormat = function (format: string) {
733
- // // eslint-disable-next-line @typescript-eslint/no-this-alias
734
- // const date: Date = this;
735
- // let result = format;
736
- // result = result.replace('yyyy', date.getFullYear().toString());
737
- // result = result.replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'));
738
- // result = result.replace('dd', date.getDate().toString().padStart(2, '0'));
739
- // result = result.replace('HH', date.getHours().toString().padStart(2, '0'));
740
- // result = result.replace('mm', date.getMinutes().toString().padStart(2, '0'));
741
- // result = result.replace('ss', date.getSeconds().toString().padStart(2, '0'));
742
- // return result;
743
- // };
744
- const DateUtilities = {
745
- equal,
746
- dayDiff,
747
- monthDiff,
748
- yearDiff,
749
- age,
750
- parseFrom,
751
- isDate,
752
- toFormat,
753
- addMiliseconds,
754
- addHours,
755
- addDays,
756
- addMonths,
757
- begin,
758
- end,
759
- timeDifference,
58
+ catch { /* ignore */ }
59
+ return 'vi';
760
60
  };
761
-
762
61
  const upload = (option) => {
763
62
  const uploadId = 'U1e09c1c0-b647-437e-995e-d7a1a1b60550';
764
63
  const promise = new Promise((resolve, reject) => {
@@ -783,13 +82,13 @@ const upload = (option) => {
783
82
  element.addEventListener('change', (evt) => {
784
83
  try {
785
84
  const target = evt.target;
786
- // Throw i18n KEY thay chuỗi đã dịch utility là pure function nên không
787
- // inject() được; consumer (component/service) chịu trách nhiệm i18n.t(err.message, { name }).
788
- // Quy ước: SdUploadError có shape { key, params } được set qua Error.cause để consumer trích xuất an toàn.
789
- const throwUploadError = (key, name) => {
790
- const err = new Error(key);
791
- err.i18nParams = { name };
792
- throw err;
85
+ // Hardcode i18n cho upload utility — pure function nên không inject() được I18nService.
86
+ // Đọc lang trực tiếp từ localStorage (cùng key với I18nService: 'sd-core.language').
87
+ // Throw plain Error(translatedText) để consumer không cần xử lý đặc biệt.
88
+ const throwUploadError = (msgKey, name) => {
89
+ const lang = getSdUploadLang();
90
+ const template = SD_UPLOAD_MESSAGES[lang][msgKey] ?? SD_UPLOAD_MESSAGES.vi[msgKey];
91
+ throw new Error(template.replace('{name}', name));
793
92
  };
794
93
  if (option?.multiple) {
795
94
  const files = [];
@@ -797,15 +96,15 @@ const upload = (option) => {
797
96
  if (file) {
798
97
  const lastDot = file.name.lastIndexOf('.');
799
98
  if (lastDot === -1) {
800
- throwUploadError('core.utility.upload.invalid-format', file.name);
99
+ throwUploadError('invalid-format', file.name);
801
100
  }
802
101
  const extension = file.name.substring(lastDot + 1);
803
102
  if (option) {
804
103
  if (option.extensions?.length && !option.extensions.some(e => e.toLowerCase() === extension.toLowerCase())) {
805
- throwUploadError('core.utility.upload.invalid-format', file.name);
104
+ throwUploadError('invalid-format', file.name);
806
105
  }
807
106
  if (option.maxSizeInMb && option.maxSizeInMb > 0 && option.maxSizeInMb * 1024 * 1024 < file.size) {
808
- throwUploadError('core.utility.upload.invalid-size', file.name);
107
+ throwUploadError('invalid-size', file.name);
809
108
  }
810
109
  if (option.validator && option.validator(file.name)) {
811
110
  // Custom validator returns its own message — pass through as-is (caller defines format).
@@ -823,15 +122,15 @@ const upload = (option) => {
823
122
  if (file) {
824
123
  const lastDot = file.name.lastIndexOf('.');
825
124
  if (lastDot === -1) {
826
- throwUploadError('core.utility.upload.invalid-format', file.name);
125
+ throwUploadError('invalid-format', file.name);
827
126
  }
828
127
  const extension = file.name.substring(lastDot + 1);
829
128
  if (option) {
830
129
  if (option.extensions?.length && !option.extensions.some(e => e.toLowerCase() === extension.toLowerCase())) {
831
- throwUploadError('core.utility.upload.invalid-format', file.name);
130
+ throwUploadError('invalid-format', file.name);
832
131
  }
833
132
  if (option.maxSizeInMb && option.maxSizeInMb > 0 && option.maxSizeInMb * 1024 * 1024 < file.size) {
834
- throwUploadError('core.utility.upload.invalid-size', file.name);
133
+ throwUploadError('invalid-size', file.name);
835
134
  }
836
135
  if (option.validator && option.validator(file.name)) {
837
136
  const message = option.validator(file.name);
@@ -1057,6 +356,11 @@ const getNestedValue = (obj, path) => {
1057
356
  // Tách chuỗi theo dấu chấm và duyệt qua từng cấp của object
1058
357
  return path.split('.').reduce((acc, part) => acc?.[part], obj);
1059
358
  };
359
+ /**
360
+ * @deprecated Use {@link Utilities} and {@link BrowserUtilities} from `@sdcorejs/utils/fns` instead.
361
+ * - upload/download/clipboard/isMobile → BrowserUtilities
362
+ * - allWithPaging (→ fetchAllByPaging) / randomId / hash / parseQueryParams / generateUuid / getNestedValue → Utilities
363
+ */
1060
364
  const SdUtilities = {
1061
365
  upload,
1062
366
  download,
@@ -1074,74 +378,14 @@ const SdUtilities = {
1074
378
  getNestedValue
1075
379
  };
1076
380
 
1077
- /**
1078
- * Color Utilities Extension
1079
- * Các tiện ích xử màu
1080
- */
1081
- /**
1082
- * Chuyển đổi màu HSL sang định dạng hex
1083
- * @param h - Hue (0-360)
1084
- * @param s - Saturation (0-100)
1085
- * @param l - Lightness (0-100)
1086
- * @returns Mã màu hex
1087
- */
1088
- function hslToHex(h, s, l) {
1089
- // Chuyển đổi sang định dạng 0-1
1090
- const hNormalized = h / 360;
1091
- const sNormalized = s / 100;
1092
- const lNormalized = l / 100;
1093
- let r, g, b;
1094
- if (sNormalized === 0) {
1095
- // Màu xám (grayscale)
1096
- r = g = b = lNormalized;
1097
- }
1098
- else {
1099
- const hue2rgb = (p, q, t) => {
1100
- if (t < 0)
1101
- t += 1;
1102
- if (t > 1)
1103
- t -= 1;
1104
- if (t < 1 / 6)
1105
- return p + (q - p) * 6 * t;
1106
- if (t < 1 / 2)
1107
- return q;
1108
- if (t < 2 / 3)
1109
- return p + (q - p) * (2 / 3 - t) * 6;
1110
- return p;
1111
- };
1112
- const q = lNormalized < 0.5
1113
- ? lNormalized * (1 + sNormalized)
1114
- : lNormalized + sNormalized - lNormalized * sNormalized;
1115
- const p = 2 * lNormalized - q;
1116
- r = hue2rgb(p, q, hNormalized + 1 / 3);
1117
- g = hue2rgb(p, q, hNormalized);
1118
- b = hue2rgb(p, q, hNormalized - 1 / 3);
1119
- }
1120
- // Chuyển đổi sang hex
1121
- const toHex = (x) => {
1122
- const hex = Math.round(x * 255).toString(16);
1123
- return hex.length === 1 ? '0' + hex : hex;
1124
- };
1125
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1126
- }
1127
- /**
1128
- * Chuyển đổi màu RGB sang hex
1129
- * @param r - Red (0-255)
1130
- * @param g - Green (0-255)
1131
- * @param b - Blue (0-255)
1132
- * @returns Mã màu hex
1133
- */
1134
- function rgbToHex(r, g, b) {
1135
- const toHex = (x) => {
1136
- const hex = Math.round(Math.max(0, Math.min(255, x))).toString(16);
1137
- return hex.length === 1 ? '0' + hex : hex;
1138
- };
1139
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1140
- }
381
+ /** @deprecated Use {@link ColorUtilities.hslToHex} from `@sdcorejs/utils/fns` instead */
382
+ const hslToHex = ColorUtilities.hslToHex;
383
+ /** @deprecated Use {@link ColorUtilities.rgbToHex} from `@sdcorejs/utils/fns` instead */
384
+ const rgbToHex = ColorUtilities.rgbToHex;
1141
385
 
1142
386
  /**
1143
387
  * Generated bundle index. Do not edit.
1144
388
  */
1145
389
 
1146
- export { ArrayUtilities, DateUtilities, NumberUtilities, SdUtilities, StringUtilities, hslToHex, rgbToHex };
390
+ export { SdUtilities, StringUtilities, hslToHex, rgbToHex };
1147
391
  //# sourceMappingURL=sd-angular-core-utilities-extensions.mjs.map