@libs-ui/utils 0.1.1-1

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 (65) hide show
  1. package/README.md +3 -0
  2. package/base64.d.ts +5 -0
  3. package/cache.d.ts +42 -0
  4. package/color.d.ts +11 -0
  5. package/communicate-micro.d.ts +16 -0
  6. package/constants.d.ts +10 -0
  7. package/crypto-3rd.d.ts +7 -0
  8. package/crypto.d.ts +8 -0
  9. package/dangerous-object.d.ts +79 -0
  10. package/date.d.ts +44 -0
  11. package/dom.d.ts +52 -0
  12. package/download.d.ts +3 -0
  13. package/esm2022/base64.mjs +43 -0
  14. package/esm2022/cache.mjs +388 -0
  15. package/esm2022/color.mjs +133 -0
  16. package/esm2022/communicate-micro.mjs +149 -0
  17. package/esm2022/constants.mjs +11 -0
  18. package/esm2022/crypto-3rd.mjs +38 -0
  19. package/esm2022/crypto.mjs +41 -0
  20. package/esm2022/dangerous-object.mjs +149 -0
  21. package/esm2022/date.mjs +191 -0
  22. package/esm2022/dom.mjs +256 -0
  23. package/esm2022/download.mjs +41 -0
  24. package/esm2022/file.mjs +90 -0
  25. package/esm2022/format-number.mjs +66 -0
  26. package/esm2022/format-text.mjs +149 -0
  27. package/esm2022/function-check-embed-frame.mjs +10 -0
  28. package/esm2022/get-smart-axis-scale.mjs +174 -0
  29. package/esm2022/helpers.mjs +651 -0
  30. package/esm2022/http-params.mjs +80 -0
  31. package/esm2022/index.mjs +30 -0
  32. package/esm2022/inject-token.mjs +5 -0
  33. package/esm2022/key-cache.mjs +31 -0
  34. package/esm2022/key-code.mjs +123 -0
  35. package/esm2022/language.mjs +70 -0
  36. package/esm2022/libs-ui-utils.mjs +5 -0
  37. package/esm2022/pattern.mjs +62 -0
  38. package/esm2022/random.mjs +42 -0
  39. package/esm2022/two-way-signal-object.mjs +131 -0
  40. package/esm2022/uri.mjs +25 -0
  41. package/esm2022/url-search-params.mjs +99 -0
  42. package/esm2022/uuid.mjs +18 -0
  43. package/esm2022/xss-filter.mjs +10 -0
  44. package/fesm2022/libs-ui-utils.mjs +3234 -0
  45. package/fesm2022/libs-ui-utils.mjs.map +1 -0
  46. package/file.d.ts +18 -0
  47. package/format-number.d.ts +2 -0
  48. package/format-text.d.ts +11 -0
  49. package/function-check-embed-frame.d.ts +2 -0
  50. package/get-smart-axis-scale.d.ts +34 -0
  51. package/helpers.d.ts +270 -0
  52. package/http-params.d.ts +37 -0
  53. package/index.d.ts +29 -0
  54. package/inject-token.d.ts +4 -0
  55. package/key-cache.d.ts +1 -0
  56. package/key-code.d.ts +122 -0
  57. package/language.d.ts +37 -0
  58. package/package.json +29 -0
  59. package/pattern.d.ts +20 -0
  60. package/random.d.ts +3 -0
  61. package/two-way-signal-object.d.ts +15 -0
  62. package/uri.d.ts +5 -0
  63. package/url-search-params.d.ts +25 -0
  64. package/uuid.d.ts +1 -0
  65. package/xss-filter.d.ts +3 -0
@@ -0,0 +1,3234 @@
1
+ import DeviceDetector from 'device-detector-js';
2
+ import Quill from 'quill';
3
+ import { Observable, Subject, fromEvent, takeUntil, filter, tap, mergeMap, startWith, finalize, lastValueFrom, timer } from 'rxjs';
4
+ import { HttpParams } from '@angular/common/http';
5
+ import { TemplateRef, ElementRef, isSignal, signal, InjectionToken } from '@angular/core';
6
+ import dayjs from 'dayjs';
7
+ import 'dayjs/locale/en';
8
+ import 'dayjs/locale/vi';
9
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
10
+ import localeData from 'dayjs/plugin/localeData';
11
+ import timezone from 'dayjs/plugin/timezone';
12
+ import updateLocale from 'dayjs/plugin/updateLocale';
13
+ import utc from 'dayjs/plugin/utc';
14
+ import CryptoES from 'crypto-es';
15
+
16
+ const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
17
+ const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
18
+ const ERROR_MESSAGE_MIN_VALID = 'i18n_message_error_input_min_value';
19
+ const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
20
+ const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
21
+ const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
22
+ const CHARACTER_DATA_EMPTY = '__';
23
+ const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
24
+ const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
25
+ const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
26
+
27
+ /* eslint-disable no-useless-escape */
28
+ const patternEmail = () => {
29
+ return /^[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]@[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]\.[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]$/;
30
+ };
31
+ const patternUrl = () => {
32
+ return /^(http|https|ftp):(\/){2}[^\s]+[.]{1}[^\s]+$/;
33
+ };
34
+ const patternHostUrl = () => {
35
+ return /^((https|http|ftp):[/]{2}[^/\s]+)/;
36
+ };
37
+ const patternDomain = () => {
38
+ return /^([a-zA-Z0-9])(([a-z0-9-]{1,61})?[a-z0-9]{1})?(\.[a-z0-9](([a-z0-9-]{1,61})?[a-z0-9]{1})?)?(\.[a-zA-Z]{2,4})+$/;
39
+ };
40
+ const patternMobilePhone = () => {
41
+ return /^(\+?84|0|84)([0-9]{9})$/;
42
+ };
43
+ const patternPhone = () => {
44
+ return /^(\+?84|[0-9]|84)([0-9]{2,9})$/;
45
+ };
46
+ const patternPhoneNormal = () => {
47
+ return /^(\+?84|[0-9]|84)([0-9]{9,10})$/;
48
+ };
49
+ const patternNumber = () => {
50
+ return /\d+/g;
51
+ };
52
+ const patternEncodeUri = () => {
53
+ return /%([0-9A-F]{2})/g;
54
+ };
55
+ const patternName = () => {
56
+ return /^\w+[A-Za-z\s\d]+$/;
57
+ };
58
+ const patternNameUtf8 = () => {
59
+ return /^[ àáảãạăắằẵặẳâầấậẫẩđèéẻẽẹêềếểễệìíỉĩịòóỏõọôồốổỗộơờớởỡợùúủũụưừứửữựỳýỷỹÀÁẢÃẠĂẮẰẴẶẲÂẦẤẬẪẨĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸA-Za-z0-9]+$/;
60
+ };
61
+ const patternNameSpecial = () => {
62
+ return /[~!@#$%^&*()-+=<>,?\/\\:;"']/;
63
+ };
64
+ const patternNameProfile = () => {
65
+ return /([\w\W\d\s]+)+/;
66
+ };
67
+ const patternEmoji = () => {
68
+ return /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
69
+ };
70
+ const patternRuleFieldReplace = () => {
71
+ return /[{]{2}[a-zA-Z_-]+[}]{2}/g;
72
+ };
73
+ const patternGetFieldByRuleFieldReplace = () => {
74
+ return /[a-zA-Z_-]+/g;
75
+ };
76
+ const patternPem = () => {
77
+ return /^(0|1):([0-9]{1,2}):(\{\{path-api\}\}):([a-zA-Z0-9\/]+)$/g;
78
+ };
79
+ const patternTax = () => {
80
+ return /^([0-9]){10}(-[0-9]{3})?$/;
81
+ };
82
+ const patternKey = () => {
83
+ return /^([0-9]){10}(-[0-9]{3})?$/;
84
+ };
85
+ const patternAccount = () => {
86
+ return /^(?=.*@)[a-z0-9@._-]{2,63}$/;
87
+ };
88
+
89
+ const highlightByKeyword = (value, search, ignoreHighlight, classHightLight) => {
90
+ if (!value) {
91
+ return CHARACTER_DATA_EMPTY;
92
+ }
93
+ try {
94
+ if (!search || ignoreHighlight) {
95
+ return value;
96
+ }
97
+ const keysReplace = getValueReplace(search, value);
98
+ if (!keysReplace || !keysReplace.length) {
99
+ return value;
100
+ }
101
+ if (!classHightLight) {
102
+ classHightLight = 'bg-[#19344a] text-white';
103
+ }
104
+ keysReplace.forEach((key) => {
105
+ const regExp = new RegExp(key, 'gi');
106
+ value = value?.replace(regExp, `<span class="${classHightLight}">$&</span>`);
107
+ });
108
+ }
109
+ catch (error) {
110
+ console.log(error);
111
+ }
112
+ finally {
113
+ // eslint-disable-next-line no-unsafe-finally
114
+ return value;
115
+ }
116
+ };
117
+ const getValueReplace = (search, value) => {
118
+ const searchConvert = deleteUnicode(search).toLowerCase();
119
+ const valueConvert = deleteUnicode(value).toLowerCase();
120
+ const keys = new Set();
121
+ let i = 0;
122
+ while ((i = valueConvert.indexOf(searchConvert, i)) >= 0) {
123
+ const endIndex = i + search.length;
124
+ keys.add(value.substring(i, endIndex));
125
+ i += +valueConvert.length;
126
+ }
127
+ return Array.from(keys);
128
+ };
129
+ const formatTextCompare = (text, options) => {
130
+ if (!text) {
131
+ return text;
132
+ }
133
+ text = text.normalize('NFC');
134
+ return formatByOptions(text, options);
135
+ };
136
+ const fullNameFormat = (value) => {
137
+ if (!value) {
138
+ return value;
139
+ }
140
+ return capitalize(value, { lowercaseOtherCharacter: true, trim: true, removeMultipleSpace: true, removeEmoji: true });
141
+ };
142
+ const capitalize = (text, options) => {
143
+ if (!text) {
144
+ return text;
145
+ }
146
+ text = formatByOptions(text, options);
147
+ return text
148
+ .split(' ')
149
+ .map((word) => firstLetterToUpperCase(word))
150
+ .join(' ');
151
+ };
152
+ const firstLetterToUpperCase = (text, options) => {
153
+ if (!text) {
154
+ return text;
155
+ }
156
+ return uppercaseByPosition(text, 0, options);
157
+ };
158
+ const uppercaseByPosition = (text, position, options) => {
159
+ if (!text) {
160
+ return text;
161
+ }
162
+ text = formatByOptions(text, options);
163
+ return `${text.substring(0, position)}${text.charAt(position).toUpperCase()}${text.substring(position + 1)}`;
164
+ };
165
+ const formatByOptions = (text, options) => {
166
+ if (!text || !options) {
167
+ return text;
168
+ }
169
+ if (options?.uppercaseOtherCharacter) {
170
+ text = text.toUpperCase();
171
+ }
172
+ if (options?.lowercaseOtherCharacter) {
173
+ text = text.toLowerCase();
174
+ }
175
+ if (options?.removeMultipleSpace) {
176
+ text = text.replace(/\u200B|\u00A0/g, '');
177
+ text = text.replace(/\s+/g, ' ');
178
+ }
179
+ if (options?.removeEmoji) {
180
+ text = removeEmoji(text);
181
+ }
182
+ if (options?.removeUnicode) {
183
+ text = deleteUnicode(text);
184
+ }
185
+ if (options?.trim) {
186
+ text = text.trim();
187
+ }
188
+ return text;
189
+ };
190
+ const escapeHtml = (str) => {
191
+ if (!str || typeof str !== 'string') {
192
+ return str;
193
+ }
194
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
195
+ };
196
+ const decodeEscapeHtml = (str) => {
197
+ const htmlTag = document.createElement('textarea');
198
+ htmlTag.innerHTML = str;
199
+ while (str) {
200
+ if (str === htmlTag.value) {
201
+ str = htmlTag.value;
202
+ htmlTag.remove();
203
+ break;
204
+ }
205
+ str = htmlTag.value;
206
+ htmlTag.innerHTML = str;
207
+ }
208
+ return str;
209
+ };
210
+ const deleteUnicode = (str) => {
211
+ str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
212
+ str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ|ễ/g, 'e');
213
+ str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
214
+ str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
215
+ str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
216
+ str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
217
+ str = str.replace(/đ/g, 'd');
218
+ str = str.replace(/À|Á|Ạ|Ả|Ã|Â|Ầ|Ấ|Ậ|Ẩ|Ẫ|Ă|Ằ|Ắ|Ặ|Ẳ|Ẵ/g, 'A');
219
+ str = str.replace(/È|É|Ẹ|Ẻ|Ẽ|Ê|Ề|Ế|Ệ|Ể|Ễ/g, 'E');
220
+ str = str.replace(/Ì|Í|Ị|Ỉ|Ĩ/g, 'I');
221
+ str = str.replace(/Ò|Ó|Ọ|Ỏ|Õ|Ô|Ồ|Ố|Ộ|Ổ|Ỗ|Ơ|Ờ|Ớ|Ợ|Ở|Ỡ/g, 'O');
222
+ str = str.replace(/Ù|Ú|Ụ|Ủ|Ũ|Ư|Ừ|Ứ|Ự|Ử|Ữ/g, 'U');
223
+ str = str.replace(/Ỳ|Ý|Ỵ|Ỷ|Ỹ/g, 'Y');
224
+ str = str.replace(/Đ/g, 'D');
225
+ str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, ''); // ̀ ́ ̃ ̉ ̣ huyền, sắc, ngã, hỏi, nặng
226
+ str = str.replace(/\u02C6|\u0306|\u031B/g, ''); // ˆ ̆ ̛ Â, Ê, Ă, Ơ, Ư
227
+ return str.normalize('NFC');
228
+ };
229
+ const removeEmoji = (text) => {
230
+ if (!text || !text.trim()) {
231
+ return text;
232
+ }
233
+ return text.replace(patternEmoji(), '');
234
+ };
235
+
236
+ /* eslint-disable @typescript-eslint/no-explicit-any */
237
+ /**
238
+ * Danh sách các constructor name nguy hiểm
239
+ */
240
+ const DANGEROUS_CONSTRUCTOR_NAMES = ['Window', 'Document', 'HTMLDocument', 'HTMLElement', 'Element', 'Node', 'Location', 'Navigator', 'Screen', 'History', 'Storage', 'Console', 'Performance'];
241
+ /**
242
+ * Kiểm tra xem đối tượng có phải là browser global object không
243
+ */
244
+ const isBrowserGlobalObject = (obj) => {
245
+ if (obj === null || typeof obj !== 'object') {
246
+ return false;
247
+ }
248
+ return obj instanceof Window || obj instanceof Document || obj === window || obj === document || obj === globalThis;
249
+ };
250
+ /**
251
+ * Kiểm tra xem đối tượng có phải là DOM object không
252
+ */
253
+ const isDOMObject = (obj) => {
254
+ if (obj === null || typeof obj !== 'object') {
255
+ return false;
256
+ }
257
+ return obj instanceof HTMLElement || obj instanceof Node || obj instanceof Element;
258
+ };
259
+ /**
260
+ * Kiểm tra xem đối tượng có phải là browser API object không
261
+ */
262
+ const isBrowserAPIObject = (obj) => {
263
+ if (obj === null || typeof obj !== 'object' || typeof window === 'undefined') {
264
+ return false;
265
+ }
266
+ return obj === window.location || obj === window.navigator || obj === window.screen || obj === window.history || obj === window.localStorage || obj === window.sessionStorage || obj === window.console || obj === window.performance;
267
+ };
268
+ /**
269
+ * Kiểm tra constructor name có nằm trong danh sách nguy hiểm không
270
+ */
271
+ const hasDangerousConstructorName = (obj) => {
272
+ if (obj === null || typeof obj !== 'object') {
273
+ return false;
274
+ }
275
+ const constructorName = obj.constructor?.name;
276
+ return constructorName && DANGEROUS_CONSTRUCTOR_NAMES.includes(constructorName);
277
+ };
278
+ /**
279
+ * Kiểm tra xem đối tượng có phải là DOM object hoặc browser object nguy hiểm không
280
+ * Những đối tượng này có thể gây ra circular reference và maximum call stack
281
+ */
282
+ const isDangerousObject = (obj) => {
283
+ if (obj === null || typeof obj !== 'object') {
284
+ return false;
285
+ }
286
+ return isBrowserGlobalObject(obj) || isDOMObject(obj) || isBrowserAPIObject(obj) || hasDangerousConstructorName(obj);
287
+ };
288
+ /**
289
+ * Kiểm tra đối tượng có phải là Angular/Framework object không
290
+ */
291
+ const isFrameworkObject = (obj) => {
292
+ if (obj === null || typeof obj !== 'object') {
293
+ return false;
294
+ }
295
+ return obj instanceof TemplateRef || obj instanceof ElementRef;
296
+ };
297
+ /**
298
+ * Kiểm tra đối tượng có phải là File/Blob object không
299
+ */
300
+ const isFile = (obj) => {
301
+ if (obj === null || typeof obj !== 'object') {
302
+ return false;
303
+ }
304
+ return obj instanceof File || obj instanceof Blob || Object.prototype.toString.call(obj) === '[object File]';
305
+ };
306
+ /**
307
+ * Kiểm tra đối tượng có phải là Built-in object không
308
+ */
309
+ const isBuiltInObject = (obj) => {
310
+ if (obj === null || typeof obj !== 'object') {
311
+ return false;
312
+ }
313
+ return obj instanceof Date || isRegExp(obj) || obj instanceof HttpParams || ('hasOwnProperty' in obj && '__ngContext__' in obj);
314
+ };
315
+ /**
316
+ * Kiểm tra đối tượng có phải là RegExp object không
317
+ */
318
+ const isRegExp = (obj) => {
319
+ return obj instanceof RegExp;
320
+ };
321
+ /**
322
+ * Kiểm tra đối tượng có phải là Async object không
323
+ */
324
+ const isAsyncObject = (obj) => {
325
+ if (obj === null || typeof obj !== 'object') {
326
+ return false;
327
+ }
328
+ return obj instanceof Promise || obj instanceof Observable;
329
+ };
330
+ /**
331
+ * Kiểm tra đối tượng có phải là Special object cần bỏ qua không
332
+ * Bao gồm: Framework objects, File objects, Built-in objects, Async objects
333
+ */
334
+ const isSpecialObject = (obj) => {
335
+ return isFrameworkObject(obj) || isFile(obj) || isBuiltInObject(obj) || isAsyncObject(obj);
336
+ };
337
+ /**
338
+ * Kiểm tra đối tượng có phải là dayjs object không
339
+ */
340
+ const isDayjsObject = (obj) => {
341
+ return dayjs.isDayjs(obj);
342
+ };
343
+ /**
344
+ * Kiểm tra đối tượng có phải là object cần bỏ qua trong quá trình convert không
345
+ */
346
+ const isSkippableObject = (obj) => {
347
+ return isDangerousObject(obj) || isSpecialObject(obj);
348
+ };
349
+ /**
350
+ * Kiểm tra đối tượng có phải là Map object không
351
+ */
352
+ const isMap = (obj) => {
353
+ return obj instanceof Map;
354
+ };
355
+ /**
356
+ * Kiểm tra đối tượng có phải là Set object không
357
+ */
358
+ const isSet = (obj) => {
359
+ return obj instanceof Set;
360
+ };
361
+ /**
362
+ * Kiểm tra đối tượng có phải là Array object không
363
+ */
364
+ const isArray = (obj) => {
365
+ return Array.isArray(obj);
366
+ };
367
+ /**
368
+ * Kiểm tra đối tượng có phải là object cần trả về nguyên trạng không
369
+ * Bao gồm: dangerous objects, special objects, dayjs objects
370
+ */
371
+ const isReturnAsIsObject = (obj) => {
372
+ return isSkippableObject(obj) || isDayjsObject(obj);
373
+ };
374
+ /**
375
+ * Kiểm tra xem đối tượng có an toàn để clone/convert không
376
+ */
377
+ const isSafeToProcess = (obj) => {
378
+ return !isDangerousObject(obj);
379
+ };
380
+
381
+ let key$1 = '12~@#loqwsxacva(3rdhaq12';
382
+ /**
383
+ * @description Thiết lập key mã hóa
384
+ * @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
385
+ */
386
+ const setKeyCrypto = (value) => {
387
+ if (value.length !== 24 && value.length !== 32) {
388
+ throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
389
+ }
390
+ key$1 = value;
391
+ };
392
+ const keyStore$1 = () => {
393
+ return key$1;
394
+ };
395
+ const encrypt = (plainData) => {
396
+ if (!keyStore$1()) {
397
+ throw Error('lỗi chưa setup key mã hóa');
398
+ }
399
+ const key = CryptoES.enc.Hex.parse(keyStore$1());
400
+ const iv = CryptoES.enc.Hex.parse(keyStore$1());
401
+ const mode = CryptoES.mode.CBC;
402
+ const padding = CryptoES.pad.Pkcs7;
403
+ const options = { iv: iv, mode: mode, padding: padding };
404
+ return CryptoES.AES.encrypt(plainData, key, options).toString();
405
+ };
406
+ const decrypt = (encryptedData) => {
407
+ if (!keyStore$1()) {
408
+ throw Error('lỗi chưa setup key mã hóa');
409
+ }
410
+ const key = CryptoES.enc.Hex.parse(keyStore$1());
411
+ const iv = CryptoES.enc.Hex.parse(keyStore$1());
412
+ const mode = CryptoES.mode.CBC;
413
+ const padding = CryptoES.pad.Pkcs7;
414
+ const options = { iv: iv, mode: mode, padding: padding };
415
+ return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
416
+ };
417
+ const md5 = (plainData) => {
418
+ return CryptoES.MD5(plainData).toString();
419
+ };
420
+
421
+ let key = '12~@#loqwsxacva(3rdhaq12';
422
+ /**
423
+ * @description Thiết lập key mã hóa
424
+ * @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
425
+ */
426
+ const setKeyCrypto3rd = (value) => {
427
+ if (value.length !== 24 && value.length !== 32) {
428
+ throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
429
+ }
430
+ key = value;
431
+ };
432
+ const keyStore = () => {
433
+ return key;
434
+ };
435
+ const encrypt3rd = (plainData) => {
436
+ if (!keyStore()) {
437
+ throw Error('lỗi chưa setup key mã hóa');
438
+ }
439
+ const key = CryptoES.enc.Hex.parse(keyStore());
440
+ const iv = CryptoES.enc.Hex.parse(keyStore());
441
+ const mode = CryptoES.mode.CBC;
442
+ const padding = CryptoES.pad.Pkcs7;
443
+ const options = { iv: iv, mode: mode, padding: padding };
444
+ return CryptoES.AES.encrypt(plainData, key, options).toString();
445
+ };
446
+ const decrypt3rd = (encryptedData) => {
447
+ if (!keyStore()) {
448
+ throw Error('lỗi chưa setup key mã hóa');
449
+ }
450
+ const key = CryptoES.enc.Hex.parse(keyStore());
451
+ const iv = CryptoES.enc.Hex.parse(keyStore());
452
+ const mode = CryptoES.mode.CBC;
453
+ const padding = CryptoES.pad.Pkcs7;
454
+ const options = { iv: iv, mode: mode, padding: padding };
455
+ return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
456
+ };
457
+
458
+ let functionCheck = () => {
459
+ return window.parent !== window.top;
460
+ };
461
+ const updateFunctionCheckEmbedFrame = (functionCustom) => {
462
+ functionCheck = functionCustom;
463
+ };
464
+ const isEmbedFrame = () => {
465
+ return functionCheck();
466
+ };
467
+
468
+ /* eslint-disable @typescript-eslint/no-explicit-any */
469
+ class UtilsCommunicateMicroKeyGlobal {
470
+ static KEY_MESSAGE_MODAL = 'LIBS_UI_MODEL_EVENT';
471
+ }
472
+ class UtilsCommunicateMicro {
473
+ static initdEvent;
474
+ static subs = new Map();
475
+ static allMessageSub = new Subject();
476
+ static initEvent(currentWindow, onDestroy) {
477
+ if (this.initdEvent) {
478
+ return;
479
+ }
480
+ this.initdEvent = true;
481
+ if (!this.subs.get(COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE)) {
482
+ this.subs.set(COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, UtilsCommunicateMicro.allMessageSub);
483
+ }
484
+ fromEvent(currentWindow, 'message')
485
+ .pipe(takeUntil(onDestroy))
486
+ .subscribe((e) => {
487
+ const event = { data: { ...e.data } };
488
+ const data = event.data;
489
+ const type = data.type;
490
+ if (!type) {
491
+ return this.allMessageSub.next(event);
492
+ }
493
+ const sub = UtilsCommunicateMicro.GetMessage(type);
494
+ if (!type || !data.response) {
495
+ return;
496
+ }
497
+ try {
498
+ if (type.includes(COMMUNICATE_MICRO_PREFIX_TYPE)) {
499
+ data.response = JSON.parse(decrypt3rd(data.response));
500
+ sub.next(event);
501
+ this.allMessageSub.next(event);
502
+ return;
503
+ }
504
+ data.response = JSON.parse(decrypt(data.response));
505
+ sub.next(event);
506
+ this.allMessageSub.next(event);
507
+ }
508
+ catch (error) {
509
+ console.log(error);
510
+ sub.next(event);
511
+ this.allMessageSub.next(event);
512
+ }
513
+ });
514
+ UtilsCommunicateMicro.GetMessage(UtilsCache.typeKeyClearLocalStorage)
515
+ .pipe(filter((e) => e.data.response !== UtilsCache.idService), takeUntil(onDestroy))
516
+ .subscribe(() => {
517
+ console.log('clear all cache by event');
518
+ UtilsCache.ClearAll();
519
+ });
520
+ }
521
+ static PostMessageToParent(data) {
522
+ data = this.convertData(data);
523
+ try {
524
+ if (isEmbedFrame()) {
525
+ window?.parent?.postMessage(data, '*');
526
+ return;
527
+ }
528
+ window?.top?.postMessage(data, '*');
529
+ }
530
+ catch (error) {
531
+ console.log(error);
532
+ }
533
+ }
534
+ static PostMessageToChildren(data) {
535
+ data = this.convertData(data);
536
+ const iframes = document.querySelectorAll('iframe');
537
+ Array.from(iframes).forEach((iframe) => {
538
+ iframe?.contentWindow?.postMessage(data, '*');
539
+ });
540
+ }
541
+ static PostMessageToOpener(data) {
542
+ if (!window.opener) {
543
+ return;
544
+ }
545
+ data = this.convertData(data);
546
+ window.opener.postMessage(data, '*');
547
+ }
548
+ static convertData(data) {
549
+ if (!data || !data.type || !data.response) {
550
+ return data;
551
+ }
552
+ data = { ...data };
553
+ const type = data.type;
554
+ if (type.includes(COMMUNICATE_MICRO_PREFIX_TYPE)) {
555
+ try {
556
+ JSON.parse(decrypt3rd(data.response));
557
+ return data;
558
+ }
559
+ catch (_) {
560
+ data.response = encrypt3rd(JSON.stringify(data.response));
561
+ return data;
562
+ }
563
+ }
564
+ try {
565
+ JSON.parse(decrypt(data.response));
566
+ return data;
567
+ }
568
+ catch (_) {
569
+ data.response = encrypt(JSON.stringify(data.response));
570
+ return data;
571
+ }
572
+ }
573
+ static GetMessage(messageType) {
574
+ if (!this.initdEvent) {
575
+ throw Error('chưa khơi tạo hàm lắng nghe sự kiện, gọi UtilsCommunicateMicro.initEvent(window) tại root component');
576
+ }
577
+ if (typeof messageType === 'string') {
578
+ let sub = this.subs.get(messageType);
579
+ if (!sub) {
580
+ sub = new Subject();
581
+ this.subs.set(messageType, sub);
582
+ return sub;
583
+ }
584
+ return sub;
585
+ }
586
+ if (!Array.isArray(messageType) || !messageType.length) {
587
+ throw new Error('message type empty');
588
+ }
589
+ if (messageType.length === 1) {
590
+ return this.GetMessage(messageType[0]);
591
+ }
592
+ const types = messageType.sort().join(';');
593
+ let sub = this.subs.get(types);
594
+ if (sub) {
595
+ return sub;
596
+ }
597
+ sub = new Subject();
598
+ this.subs.set(types, sub);
599
+ this.initSubject(sub, messageType);
600
+ return sub;
601
+ }
602
+ static initSubject(subRoot, messageType) {
603
+ messageType.forEach((key) => {
604
+ this.GetMessage(key).subscribe((e) => {
605
+ subRoot.next(e);
606
+ });
607
+ });
608
+ }
609
+ }
610
+
611
+ class UtilsLanguageConstants {
612
+ static VI = 'vi'; // Tiếng Việt
613
+ static EN = 'en'; // Tiếng Anh
614
+ static FR = 'fr'; // Tiếng Pháp
615
+ static DE = 'de'; // Tiếng Đức
616
+ static ES = 'es'; // Tiếng Tây Ban Nha
617
+ static ZH = 'zh'; // Tiếng Trung (Giản thể)
618
+ static ZH_TW = 'zh-TW'; // Tiếng Trung (Phồn thể)
619
+ static JA = 'ja'; // Tiếng Nhật
620
+ static KO = 'ko'; // Tiếng Hàn
621
+ static RU = 'ru'; // Tiếng Nga
622
+ static IT = 'it'; // Tiếng Ý
623
+ static PT = 'pt'; // Tiếng Bồ Đào Nha
624
+ static TH = 'th'; // Tiếng Thái
625
+ static ID = 'id'; // Tiếng Indonesia
626
+ static MS = 'ms'; // Tiếng Malaysia
627
+ static AR = 'ar'; // Tiếng Ả Rập
628
+ static HI = 'hi'; // Tiếng Hindi
629
+ static BN = 'bn'; // Tiếng Bengal
630
+ static TR = 'tr'; // Tiếng Thổ Nhĩ Kỳ
631
+ static NL = 'nl'; // Tiếng Hà Lan
632
+ static KM = 'km'; // Tiếng Khmer (Campuchia)
633
+ static LO = 'lo'; // Tiếng Lào
634
+ static MY = 'my'; // Tiếng Miến Điện (Myanmar)
635
+ static TL = 'tl'; // Tiếng Tagalog (Philippines)
636
+ static CEB = 'ceb'; // Tiếng Cebuano (Philippines)
637
+ static JV = 'jv'; // Tiếng Java (Indonesia)
638
+ static SU = 'su'; // Tiếng Sundanese (Indonesia)
639
+ // Ngôn ngữ mặc định
640
+ static defaultLang = UtilsLanguageConstants.VI;
641
+ // Danh sách các ngôn ngữ được hỗ trợ
642
+ static supportedLanguages = new Set([
643
+ UtilsLanguageConstants.VI,
644
+ UtilsLanguageConstants.EN,
645
+ UtilsLanguageConstants.FR,
646
+ UtilsLanguageConstants.DE,
647
+ UtilsLanguageConstants.ES,
648
+ UtilsLanguageConstants.ZH,
649
+ UtilsLanguageConstants.ZH_TW,
650
+ UtilsLanguageConstants.JA,
651
+ UtilsLanguageConstants.KO,
652
+ UtilsLanguageConstants.RU,
653
+ UtilsLanguageConstants.IT,
654
+ UtilsLanguageConstants.PT,
655
+ UtilsLanguageConstants.TH,
656
+ UtilsLanguageConstants.ID,
657
+ UtilsLanguageConstants.MS,
658
+ UtilsLanguageConstants.AR,
659
+ UtilsLanguageConstants.HI,
660
+ UtilsLanguageConstants.BN,
661
+ UtilsLanguageConstants.TR,
662
+ UtilsLanguageConstants.NL,
663
+ UtilsLanguageConstants.KM,
664
+ UtilsLanguageConstants.LO,
665
+ UtilsLanguageConstants.MY,
666
+ UtilsLanguageConstants.TL,
667
+ UtilsLanguageConstants.CEB,
668
+ UtilsLanguageConstants.JV,
669
+ UtilsLanguageConstants.SU,
670
+ ]);
671
+ /**
672
+ * Kiểm tra xem ngôn ngữ đầu vào có được hỗ trợ không
673
+ * @param lang Mã ngôn ngữ cần kiểm tra
674
+ * @returns True nếu được hỗ trợ, False nếu không
675
+ */
676
+ static isSupported(lang) {
677
+ return UtilsLanguageConstants.supportedLanguages.has(lang);
678
+ }
679
+ }
680
+
681
+ const uuid = () => {
682
+ const timestamp = performance.now() * 1000;
683
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
684
+ let randomString = '';
685
+ for (let i = 0; i < 5; i++) {
686
+ randomString += chars.charAt(Math.floor(Math.random() * chars.length));
687
+ }
688
+ const baseString = `${Math.floor(timestamp)}-${randomString}`;
689
+ const charArray = baseString.split('');
690
+ for (let i = charArray.length - 1; i > 0; i--) {
691
+ const j = Math.floor(Math.random() * (i + 1));
692
+ [charArray[i], charArray[j]] = [charArray[j], charArray[i]];
693
+ }
694
+ const shuffledString = charArray.join('');
695
+ return md5(shuffledString);
696
+ };
697
+
698
+ /* eslint-disable no-async-promise-executor */
699
+ /* eslint-disable @typescript-eslint/no-explicit-any */
700
+ class UtilsCache {
701
+ static CACHE_EXPIRE_TIME_DEFAULT = 5 * 60;
702
+ static CACHE_EXPIRE_NONE = -1;
703
+ static idService = uuid();
704
+ static typeKeyClearLocalStorage = 'LIBS_UI_CLEAR_LOCAL_STORAGE_EVENT';
705
+ static languageKeyCache = 'SR3xQKxHgffiCevPQRralg';
706
+ static listKeyKeepWhenClearALll = Array();
707
+ static initdEvent;
708
+ static storage;
709
+ static dbName = 'libs-ui-cache';
710
+ static itemIndexByKey = 'key';
711
+ static dbVersion = 1;
712
+ static db = null;
713
+ static init(config) {
714
+ if (this.initdEvent) {
715
+ return;
716
+ }
717
+ this.initdEvent = true;
718
+ if (config.indexedDBName) {
719
+ this.dbName = config.indexedDBName;
720
+ }
721
+ if (config.typeKeyClearLocalStorage) {
722
+ this.typeKeyClearLocalStorage = config.typeKeyClearLocalStorage;
723
+ }
724
+ if (config.listKeyKeepWhenClearAll) {
725
+ this.listKeyKeepWhenClearALll = config.listKeyKeepWhenClearAll;
726
+ }
727
+ if (config.languageKeyCache) {
728
+ this.languageKeyCache = config.languageKeyCache;
729
+ }
730
+ }
731
+ static setLang(lang) {
732
+ if (!UtilsLanguageConstants.isSupported(lang)) {
733
+ throw Error(`Language not support ${lang}`);
734
+ }
735
+ this.Set(this.languageKeyCache, lang, this.CACHE_EXPIRE_NONE);
736
+ }
737
+ static getLang() {
738
+ return this.Get(this.languageKeyCache, UtilsLanguageConstants.defaultLang);
739
+ }
740
+ static openDB() {
741
+ return new Promise((resolve) => {
742
+ const request = indexedDB.open(this.dbName, this.dbVersion);
743
+ request.onupgradeneeded = (event) => {
744
+ const db = event.target.result;
745
+ if (!db.objectStoreNames.contains(this.dbName)) {
746
+ const objectStore = db.createObjectStore(this.dbName, { keyPath: this.itemIndexByKey });
747
+ objectStore.createIndex(this.itemIndexByKey, this.itemIndexByKey, { unique: true });
748
+ }
749
+ };
750
+ request.onsuccess = () => {
751
+ this.db = request.result;
752
+ resolve(true);
753
+ };
754
+ request.onerror = (event) => {
755
+ console.trace('Error opening IndexedDB:', event);
756
+ resolve(false);
757
+ };
758
+ });
759
+ }
760
+ static async getObjectStore() {
761
+ if (!this.db) {
762
+ await this.openDB();
763
+ }
764
+ const transaction = this.db?.transaction([this.dbName], 'readwrite');
765
+ if (!transaction) {
766
+ return null;
767
+ }
768
+ return transaction.objectStore(this.dbName);
769
+ }
770
+ static get LocalStorage() {
771
+ try {
772
+ if (typeof window.localStorage !== 'undefined') {
773
+ const OS = getPlatFromBrowser();
774
+ if (OS.includes('Safari') && parseFloat(OS.split(' ').pop() || '0') >= 16) {
775
+ return this.getLocalStorageFakeOnSafari();
776
+ }
777
+ return localStorage;
778
+ }
779
+ return this.getLocalStorageFake();
780
+ }
781
+ catch (error) {
782
+ console.trace(`LocalStorage `, error);
783
+ return this.getLocalStorageFake();
784
+ }
785
+ }
786
+ static getLocalStorageFakeOnSafari() {
787
+ if (typeof window.localStorage !== 'undefined' && !this.storage && Object.keys(localStorage).length) {
788
+ this.storage = { ...localStorage };
789
+ }
790
+ return {
791
+ setItem: (key, value) => {
792
+ localStorage.setItem(key, value);
793
+ this.storage[key] = value;
794
+ },
795
+ getItem: (key) => {
796
+ const value = localStorage.getItem(key);
797
+ if (value) {
798
+ return value;
799
+ }
800
+ if (!this.storage || isNil(this.storage[key])) {
801
+ return null;
802
+ }
803
+ localStorage.setItem(key, this.storage[key]);
804
+ return this.storage?.[key];
805
+ },
806
+ removeItem: (key) => {
807
+ delete this.storage?.[key];
808
+ localStorage.removeItem(key);
809
+ },
810
+ clear: () => {
811
+ this.storage = {};
812
+ localStorage.clear();
813
+ },
814
+ };
815
+ }
816
+ static getLocalStorageFake() {
817
+ if (!this.storage) {
818
+ this.storage = {};
819
+ }
820
+ return {
821
+ setItem: (key, value) => {
822
+ this.storage[key] = value;
823
+ },
824
+ getItem: (key) => {
825
+ return this.storage?.[key];
826
+ },
827
+ removeItem: (key) => {
828
+ delete this.storage?.[key];
829
+ },
830
+ clear: () => {
831
+ this.storage = {};
832
+ },
833
+ };
834
+ }
835
+ static async GetAsync(key, default_value, isKeyMD5 = false) {
836
+ key = isKeyMD5 ? key : md5(key);
837
+ return new Promise(async (resolve) => {
838
+ const objectStore = await this.getObjectStore();
839
+ if (!objectStore) {
840
+ return resolve(default_value);
841
+ }
842
+ const request = objectStore.get(key);
843
+ request.onsuccess = () => {
844
+ if (!request.result) {
845
+ return resolve(default_value);
846
+ }
847
+ const data = JSON.parse(decrypt(request.result.value));
848
+ if (data.expire === this.CACHE_EXPIRE_NONE) {
849
+ return resolve(data.json);
850
+ }
851
+ const currentMillisecond = new Date().valueOf() / 1000;
852
+ if (data.expire < currentMillisecond) {
853
+ return resolve(default_value);
854
+ }
855
+ return resolve(data.json);
856
+ };
857
+ request.onerror = () => {
858
+ console.trace(`Get key ${key} Error, return default value: ${default_value}`);
859
+ return resolve(default_value);
860
+ };
861
+ });
862
+ }
863
+ static Get(key, default_value) {
864
+ // support cho những file không thể inject UtilsCache
865
+ if (!key) {
866
+ return this.GetDefaultValueBySpecificKey(key, default_value);
867
+ }
868
+ const cachedData = this.LocalStorage.getItem(key);
869
+ if (!cachedData) {
870
+ return this.GetDefaultValueBySpecificKey(key, default_value);
871
+ }
872
+ try {
873
+ const data = JSON.parse(decrypt(cachedData));
874
+ if (data.expire === this.CACHE_EXPIRE_NONE) {
875
+ return data.value ?? default_value;
876
+ }
877
+ const currentMillisecond = new Date().valueOf() / 1000;
878
+ if (data.expire < currentMillisecond) {
879
+ return this.GetDefaultValueBySpecificKey(key, default_value);
880
+ }
881
+ return data.value;
882
+ }
883
+ catch (error) {
884
+ console.trace(`Get key ${key} Error, return default value: ${default_value}`, error);
885
+ return this.GetDefaultValueBySpecificKey(key, default_value);
886
+ }
887
+ }
888
+ static GetDefaultValueBySpecificKey(key, default_value) {
889
+ return default_value;
890
+ }
891
+ static async SetAsync(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT, isKeyMD5 = false) {
892
+ // support inject UtilsCache
893
+ return new Promise(async (resolve) => {
894
+ const objectStore = await this.getObjectStore();
895
+ key = isKeyMD5 ? key : md5(key);
896
+ try {
897
+ const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
898
+ const data = {
899
+ value: encrypt(JSON.stringify({ json: value, expire: currentMillisecond })),
900
+ };
901
+ data[this.itemIndexByKey] = key;
902
+ if (!objectStore) {
903
+ console.trace(`Can not open object store`);
904
+ return resolve({ key, messageError: 'Can not open object store' });
905
+ }
906
+ const request = objectStore?.put(data);
907
+ request.onsuccess = () => {
908
+ console.log(`Set key ${key} Success`);
909
+ resolve(request.result);
910
+ };
911
+ request.onerror = (error) => {
912
+ console.trace(`Set key ${key} Error`);
913
+ resolve({ key, messageError: get(error, 'message') });
914
+ };
915
+ }
916
+ catch (error) {
917
+ console.trace(`Set key ${key} Error`);
918
+ resolve({ key, messageError: get(error, 'message') });
919
+ }
920
+ });
921
+ }
922
+ static Set(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT) {
923
+ // support cho những file không inject UtilsCache
924
+ const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
925
+ const data = {
926
+ value: value,
927
+ expire: currentMillisecond,
928
+ };
929
+ try {
930
+ this.LocalStorage.setItem(key, encrypt(JSON.stringify(data)));
931
+ return true;
932
+ }
933
+ catch (error) {
934
+ console.trace(`Set key ${key} Error`, error);
935
+ return false;
936
+ }
937
+ }
938
+ static async ClearAsync(key, isMD5 = false) {
939
+ return new Promise(async (resolve) => {
940
+ const objectStore = await this.getObjectStore();
941
+ if (!objectStore) {
942
+ return resolve();
943
+ }
944
+ const request = objectStore.delete(isMD5 ? key : md5(key));
945
+ request.onsuccess = () => {
946
+ resolve({ clearSuccess: true });
947
+ };
948
+ request.onerror = (event) => {
949
+ console.trace('Error deleting Key:', get(event.target.error, 'message'));
950
+ resolve({ messageError: get(event.target.error, 'message'), clearSuccess: false });
951
+ };
952
+ });
953
+ }
954
+ static Clear(key) {
955
+ if (key.includes('kc-callback-')) {
956
+ return;
957
+ }
958
+ this.LocalStorage.removeItem(key);
959
+ }
960
+ static ClearAllAsync() {
961
+ return new Promise(async (resolve) => {
962
+ const objectStore = await this.getObjectStore();
963
+ if (!objectStore) {
964
+ return resolve();
965
+ }
966
+ const request = objectStore.clear();
967
+ request.onsuccess = () => {
968
+ console.log('clear all successfully');
969
+ resolve({ clearSuccess: true });
970
+ };
971
+ request.onerror = (event) => {
972
+ console.trace('Error deleting key:', get(event.target.error, 'message'));
973
+ resolve({ messageError: get(event.target.error, 'message'), clearSuccess: false });
974
+ };
975
+ });
976
+ }
977
+ static ClearAll() {
978
+ if (isEmbedFrame()) {
979
+ const data = {
980
+ type: this.typeKeyClearLocalStorage,
981
+ response: {
982
+ idEvent: this.idService,
983
+ },
984
+ };
985
+ UtilsCommunicateMicro.PostMessageToParent(data);
986
+ }
987
+ const keys = [...this.listKeyKeepWhenClearALll];
988
+ Object.keys(this.LocalStorage).forEach((key) => {
989
+ if (key.includes('kc-callback-')) {
990
+ keys.push(key);
991
+ }
992
+ });
993
+ const stores = this.GetDataByKeys(Array.from(keys));
994
+ this.LocalStorage.clear();
995
+ this.SetDataByKey(stores);
996
+ }
997
+ static GetDataByKeys(keys) {
998
+ const stores = new Map();
999
+ keys.forEach((key) => {
1000
+ if (key.includes('kc-callback-')) {
1001
+ stores.set(key, this.LocalStorage.getItem(key));
1002
+ return;
1003
+ }
1004
+ stores.set(key, this.Get(key));
1005
+ });
1006
+ return stores;
1007
+ }
1008
+ static SetDataByKey(stores) {
1009
+ stores.forEach((value, key) => {
1010
+ if (key.includes('kc-callback-')) {
1011
+ this.LocalStorage.setItem(key, value);
1012
+ return;
1013
+ }
1014
+ if (key === this.languageKeyCache) {
1015
+ return this.setLang(value);
1016
+ }
1017
+ this.Set(key, value, this.CACHE_EXPIRE_NONE);
1018
+ });
1019
+ }
1020
+ static DeleteKeyStartWithAsync(keyCacheStartWith, isKeyMD5 = false) {
1021
+ return new Promise(async (resolve) => {
1022
+ const objectStore = await this.getObjectStore();
1023
+ if (!objectStore) {
1024
+ return resolve({});
1025
+ }
1026
+ // Lấy tất cả các keys từ index
1027
+ const request = objectStore.getAll();
1028
+ keyCacheStartWith = isKeyMD5 ? keyCacheStartWith : md5(keyCacheStartWith);
1029
+ request.onsuccess = (e) => {
1030
+ const data = e.target.result;
1031
+ if (!Array.isArray(data)) {
1032
+ return resolve({});
1033
+ }
1034
+ data.forEach((obj) => {
1035
+ if (obj[this.itemIndexByKey].startsWith(keyCacheStartWith)) {
1036
+ this.ClearAsync(obj[this.itemIndexByKey], true);
1037
+ }
1038
+ });
1039
+ return resolve({});
1040
+ };
1041
+ request.onerror = () => {
1042
+ return resolve({});
1043
+ };
1044
+ });
1045
+ }
1046
+ static DeleteKeyStartWith(keyCache, isMD5 = false) {
1047
+ keyCache = isMD5 ? md5(keyCache) : keyCache;
1048
+ const keys = Object.keys(this.LocalStorage);
1049
+ if (!keys || !keys.length) {
1050
+ return;
1051
+ }
1052
+ keys.forEach((key) => {
1053
+ if (key.startsWith(keyCache)) {
1054
+ this.Clear(key);
1055
+ }
1056
+ });
1057
+ }
1058
+ static DeleteDatabaseIndexDB(dbName) {
1059
+ return new Promise((resolve) => {
1060
+ dbName = (dbName || this.dbName);
1061
+ const request = indexedDB.deleteDatabase(dbName);
1062
+ request.onsuccess = () => {
1063
+ console.trace('Database deleted successfully');
1064
+ resolve({ deleteSuccess: true });
1065
+ };
1066
+ request.onerror = (event) => {
1067
+ const error = event.target.error;
1068
+ console.trace('Error deleting database:', error);
1069
+ resolve({ messageError: get(error || {}, 'message'), deleteSuccess: false });
1070
+ };
1071
+ request.onblocked = () => {
1072
+ console.trace('Delete request is blocked');
1073
+ resolve({ messageError: 'Delete request is blocked', deleteSuccess: false });
1074
+ };
1075
+ });
1076
+ }
1077
+ }
1078
+
1079
+ dayjs.extend(localeData);
1080
+ dayjs.extend(updateLocale);
1081
+ dayjs.extend(utc);
1082
+ dayjs.extend(timezone);
1083
+ dayjs.extend(customParseFormat);
1084
+ let timeZoneSetup = 'Asia/Ho_Chi_Minh';
1085
+ const setDefaultTimeZone = (localeZone = timeZoneSetup) => {
1086
+ timeZoneSetup = localeZone;
1087
+ dayjs.tz.setDefault(localeZone);
1088
+ };
1089
+ let functionFormatDate = undefined;
1090
+ const updateFunctionFormatDate = (functionCustom) => {
1091
+ functionFormatDate = functionCustom;
1092
+ };
1093
+ /**
1094
+ * @description Lấy đối tượng dayjs theo config
1095
+ * @param config nếu không có config sẽ trả về đối tượng dayjs là thời gian hiện tại
1096
+ * @param config.date thời gian cần lấy
1097
+ * @param config.returnDayjsIfConfigDateNotExist true nếu muốn trả về đối tượng dayjs nếu config.date không có
1098
+ * @param config.utc true nếu muốn lấy thời gian UTC
1099
+ * @param config.formatOfDate định dạng thời gian đang được truyền vào
1100
+ */
1101
+ const getDayjs = (config) => {
1102
+ // helper cast để tránh lặp lại kiểu điều kiện ở các return
1103
+ const out = (v) => v;
1104
+ if (!config) {
1105
+ return out(dayjs().tz());
1106
+ }
1107
+ config.date = !config.date && config.returnDayjsIfConfigDateNotExist ? dayjs().tz() : config.date;
1108
+ if (typeof config.date === 'number') {
1109
+ config.date = dayjs.unix(config.date);
1110
+ }
1111
+ if (!config.date) {
1112
+ return out(undefined);
1113
+ }
1114
+ let { date, utc, formatOfDate } = config;
1115
+ while (isSignal(date)) {
1116
+ date = date();
1117
+ }
1118
+ while (isSignal(utc)) {
1119
+ utc = utc();
1120
+ }
1121
+ while (isSignal(formatOfDate)) {
1122
+ formatOfDate = formatOfDate();
1123
+ }
1124
+ if (utc) {
1125
+ if (formatOfDate) {
1126
+ return out(dayjs(date, formatOfDate).utc());
1127
+ }
1128
+ const dateInputIsUTC = (dayjs.isDayjs(date) && date.isUTC()) || (typeof date === 'string' && date.includes('Z'));
1129
+ if (dateInputIsUTC) {
1130
+ return out(dayjs(date));
1131
+ }
1132
+ return out(dayjs(date).utc());
1133
+ }
1134
+ if (typeof date === 'string' && !date.includes('Z') && !date.includes('+')) {
1135
+ return out(formatOfDate ? dayjs.tz(date, formatOfDate, config.localeZone || timeZoneSetup) : dayjs.tz(date, config.localeZone || timeZoneSetup));
1136
+ }
1137
+ return out((formatOfDate ? dayjs(date, formatOfDate) : dayjs(date)).tz());
1138
+ };
1139
+ /**
1140
+ * @description Kiểm tra xem hai ngày có khác nhau không (khác ngày trong tuần)
1141
+ * @param date1 ngày thứ nhất cần so sánh
1142
+ * @param date2 ngày thứ hai cần so sánh
1143
+ * @returns true nếu hai ngày khác nhau, false nếu giống nhau hoặc không thể so sánh
1144
+ */
1145
+ const isDifferenceDay = (date1, date2) => {
1146
+ if (isDifferenceMonth(date1, date2)) {
1147
+ return true;
1148
+ }
1149
+ const day1 = getDayjs({ date: date1 })?.day();
1150
+ const day2 = getDayjs({ date: date2 })?.day();
1151
+ return day1 !== day2;
1152
+ };
1153
+ /**
1154
+ * @description Kiểm tra xem hai ngày có khác tháng không
1155
+ * @param date1 ngày thứ nhất cần so sánh
1156
+ * @param date2 ngày thứ hai cần so sánh
1157
+ * @returns true nếu hai ngày khác tháng, false nếu cùng tháng hoặc không thể so sánh
1158
+ */
1159
+ const isDifferenceMonth = (date1, date2) => {
1160
+ if (isDifferenceYear(date1, date2)) {
1161
+ return true;
1162
+ }
1163
+ const month1 = getDayjs({ date: date1 })?.month();
1164
+ const month2 = getDayjs({ date: date2 })?.month();
1165
+ return month1 !== month2;
1166
+ };
1167
+ /**
1168
+ * @description Kiểm tra xem hai ngày có khác năm không
1169
+ * @param date1 ngày thứ nhất cần so sánh
1170
+ * @param date2 ngày thứ hai cần so sánh
1171
+ * @returns true nếu hai ngày khác năm, false nếu cùng năm hoặc không thể so sánh
1172
+ */
1173
+ const isDifferenceYear = (date1, date2) => {
1174
+ const year1 = getDayjs({ date: date1 })?.year();
1175
+ const year2 = getDayjs({ date: date2 })?.year();
1176
+ return year1 !== year2;
1177
+ };
1178
+ /**
1179
+ * @description Lấy ra chuỗi thời gian hiển thị theo định dạng và ngôn ngữ
1180
+ * @param date thời gian cần định dạng
1181
+ * @param format định dạng thời gian muốn lấy ra
1182
+ * @param lang lấy theo ngôn ngữ
1183
+ */
1184
+ const formatDate = (date, formatOutput = 'YYYY/MM/DD HH:mm', lang, formatInput) => {
1185
+ if (!date) {
1186
+ return '';
1187
+ }
1188
+ lang = lang || UtilsCache.getLang();
1189
+ if (functionFormatDate) {
1190
+ return functionFormatDate(date, formatOutput, lang);
1191
+ }
1192
+ date = getDayjs({ date, formatOfDate: formatInput }).locale(lang);
1193
+ if (lang === 'vi') {
1194
+ dayjs.updateLocale('vi', {
1195
+ monthsShort: 'Thg 1_Thg 2_Thg 3_Thg 4_Thg 5_Thg 6_Thg 7_Thg 8_Thg 9_Thg 10_Thg 11_Thg 12'.split('_'),
1196
+ });
1197
+ }
1198
+ return date.format(getFormatData(getTypeByFormat(formatOutput), lang)) || '';
1199
+ };
1200
+ const getTypeByFormat = (format) => {
1201
+ if (format === 'dm' || format === 'dmy' || format === 'dmy hm' || format === 'my') {
1202
+ return format;
1203
+ }
1204
+ if (format === 'DD/MM/YYYY' || format === 'YYYY-MM-DD' || format === 'dd/MM/yyyy' || format === 'dd-MM-yyyy' || format === 'dd/mm/yyyy') {
1205
+ return 'dmy';
1206
+ }
1207
+ if (format === 'MM-DD' || format === 'dd/MM' || format === 'dd/mm') {
1208
+ return 'dm';
1209
+ }
1210
+ if (format === 'M/YYYY' || format === 'YYYY-MM' || format === 'MM/yyyy') {
1211
+ return 'my';
1212
+ }
1213
+ if (format === 'YYYY/MM/DD hh:mm:ss' || format === 'dmy hms' || format === 'dd/mm/yyyy hh:mm:ss') {
1214
+ return 'dmy hms';
1215
+ }
1216
+ if (format === 'hh:mm' || format === 'HH:mm') {
1217
+ return 'hh:mm';
1218
+ }
1219
+ if (format === 'hh:mm A' || format === 'HH:mm A') {
1220
+ return 'hh:mm A';
1221
+ }
1222
+ return 'dmy hm';
1223
+ };
1224
+ const getFormatData = (type, lang) => {
1225
+ switch (type) {
1226
+ case 'dm':
1227
+ if (lang === 'vi') {
1228
+ return 'D MMM';
1229
+ }
1230
+ return 'MMM D';
1231
+ case 'dmy':
1232
+ if (lang === 'vi') {
1233
+ return 'D MMM, YYYY';
1234
+ }
1235
+ return 'MMM D, YYYY';
1236
+ case 'dmy hm':
1237
+ if (lang === 'vi') {
1238
+ return 'D MMM, YYYY HH:mm';
1239
+ }
1240
+ return 'MMM D, YYYY HH:mm';
1241
+ case 'my':
1242
+ if (lang === 'vi') {
1243
+ return 'MMM, YYYY';
1244
+ }
1245
+ return 'MMM YYYY ';
1246
+ case 'dmy hms':
1247
+ if (lang === 'vi') {
1248
+ return 'D MMM, YYYY HH:mm:ss';
1249
+ }
1250
+ return 'MMM D, YYYY HH:mm:ss';
1251
+ case 'hh:mm':
1252
+ case 'HH:mm':
1253
+ return 'HH:mm';
1254
+ case 'hh:mm A':
1255
+ case 'HH:mm A':
1256
+ return 'HH:mm A';
1257
+ }
1258
+ };
1259
+
1260
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1261
+ const UtilsHttpParamsRequestInstance = (options, instance) => {
1262
+ return new UtilsHttpParamsRequest(options, instance);
1263
+ };
1264
+ class UtilsHttpParamsRequest extends HttpParams {
1265
+ params = new HttpParams();
1266
+ constructor(options, instance) {
1267
+ super(options);
1268
+ if (!instance) {
1269
+ this.params = new HttpParams(options);
1270
+ return;
1271
+ }
1272
+ if (instance instanceof UtilsHttpParamsRequest) {
1273
+ this.params = instance.getInstance();
1274
+ return;
1275
+ }
1276
+ if (instance instanceof HttpParams) {
1277
+ this.params = instance;
1278
+ return;
1279
+ }
1280
+ }
1281
+ getInstance() {
1282
+ return this.params;
1283
+ }
1284
+ setInstance(instance) {
1285
+ if (instance instanceof UtilsHttpParamsRequest) {
1286
+ this.params = instance.getInstance();
1287
+ return;
1288
+ }
1289
+ if (instance instanceof HttpParams) {
1290
+ this.params = instance;
1291
+ return;
1292
+ }
1293
+ }
1294
+ set(param, value) {
1295
+ this.params = this.params.set(param, value);
1296
+ return this;
1297
+ }
1298
+ has(param) {
1299
+ return this.params.has(param);
1300
+ }
1301
+ get(param) {
1302
+ return this.params.get(param);
1303
+ }
1304
+ getAll(param) {
1305
+ return this.params.getAll(param);
1306
+ }
1307
+ keys() {
1308
+ return this.params.keys();
1309
+ }
1310
+ append(param, value) {
1311
+ this.params = this.params.append(param, value);
1312
+ return this;
1313
+ }
1314
+ appendAll(params) {
1315
+ this.params = this.params.appendAll(params);
1316
+ return this;
1317
+ }
1318
+ delete(param, value) {
1319
+ this.params = this.params.delete(param, value);
1320
+ return this;
1321
+ }
1322
+ toString() {
1323
+ return this.params.toString();
1324
+ }
1325
+ }
1326
+ // Demo su dung GET_PATH_VARIABLE
1327
+ // interface Person {
1328
+ // name: string;
1329
+ // age: number;
1330
+ // location: string;
1331
+ // }
1332
+ // type c = GET_PATH_VARIABLE<Person, unknown>;
1333
+ // const a: c = {
1334
+ // "pathVariable-age": 1,
1335
+ // "pathVariable-location": '12',
1336
+ // "pathVariable-name": '124',
1337
+ // };
1338
+
1339
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1340
+ /**
1341
+ * Chuyển đổi một đối tượng hoặc giá trị thành signal
1342
+ * @param data Dữ liệu cần chuyển đổi thành signal
1343
+ * @param cloneDeepIfNotSignal Có thực hiện sao chép sâu dữ liệu trước khi chuyển đổi hay không nếu data không phải signal. Mặc định là true.
1344
+ * Đặt false nếu muốn giữ nguyên tham chiếu đến dữ liệu gốc.
1345
+ * Nếu muốn sao chép sâu đối tượng signal thì đặt cloneDeepIfNotSignal là true và acceptConvertObjectInnerWritableSignal là true.
1346
+ * @param isSignalPrimitiveType Có chuyển đổi các giá trị nguyên thủy (string, number, boolean) thành signal hay không. Mặc định là false.
1347
+ * Đặt true nếu muốn bọc các giá trị nguyên thủy trong signal.
1348
+ * @param acceptConvertObjectInnerWritableSignal Có tiếp tục tìm kiếm và chuyển đổi các đối tượng bên trong signal hay không. Mặc định là false.
1349
+ * Đặt true nếu muốn tìm và chuyển đổi các đối tượng bên trong signal hoặc chính nó thành signal mới.
1350
+ * @returns Dữ liệu đã được chuyển đổi theo kiểu T
1351
+ */
1352
+ const convertObjectToSignal = (data, cloneDeepIfNotSignal = true, isSignalPrimitiveType = false, acceptConvertObjectInnerWritableSignal = false, seen = new WeakMap()) => {
1353
+ if ((data === null || typeof data !== 'object') && !isSignal(data)) {
1354
+ return (isSignalPrimitiveType ? signal(data) : data);
1355
+ }
1356
+ if (seen.has(data)) {
1357
+ return seen.get(data);
1358
+ }
1359
+ if (isSignal(data)) {
1360
+ if (!acceptConvertObjectInnerWritableSignal) {
1361
+ return data;
1362
+ }
1363
+ seen.set(data, convertObjectToSignal(data(), cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal, seen));
1364
+ return seen.get(data);
1365
+ }
1366
+ if (isArray(data)) {
1367
+ seen.set(data, signal(data.map((item) => convertObjectToSignal(item, cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal, seen))));
1368
+ return seen.get(data);
1369
+ }
1370
+ if (isMap(data)) {
1371
+ const mapCopy = new Map();
1372
+ Array.from(data.keys()).forEach((key) => {
1373
+ mapCopy.set(key, convertObjectToSignal(data.get(key), cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal));
1374
+ });
1375
+ seen.set(data, signal(mapCopy));
1376
+ return seen.get(data);
1377
+ }
1378
+ // Bỏ qua các đối tượng async
1379
+ if (isAsyncObject(data)) {
1380
+ return data;
1381
+ }
1382
+ data = signal(cloneDeepIfNotSignal ? { ...data } : data);
1383
+ seen.set(data, data);
1384
+ for (const key in data()) {
1385
+ const value = data()[key];
1386
+ // Bỏ qua các đối tượng nguy hiểm và đặc biệt
1387
+ if (isReturnAsIsObject(value)) {
1388
+ continue;
1389
+ }
1390
+ if (Object.prototype.hasOwnProperty.call(data(), key)) {
1391
+ data()[key] = convertObjectToSignal(value, cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal);
1392
+ }
1393
+ }
1394
+ return data;
1395
+ };
1396
+ const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap()) => {
1397
+ let ignoreCheckSeenHasDataAfterWhile = false;
1398
+ while (isSignal(data) && !seen.has(data)) {
1399
+ const dataConvert = data();
1400
+ if (typeof dataConvert === 'object') {
1401
+ if (dataConvert === null || isReturnAsIsObject(dataConvert)) {
1402
+ return dataConvert;
1403
+ }
1404
+ seen.set(dataConvert, dataConvert);
1405
+ data = dataConvert;
1406
+ ignoreCheckSeenHasDataAfterWhile = true;
1407
+ break;
1408
+ }
1409
+ seen.set(data, dataConvert);
1410
+ data = dataConvert;
1411
+ }
1412
+ if (data === null || typeof data === 'function' || typeof data !== 'object' || isReturnAsIsObject(data)) {
1413
+ return data;
1414
+ }
1415
+ if (!ignoreCheckSeenHasDataAfterWhile && seen.has(data)) {
1416
+ return seen.get(data);
1417
+ }
1418
+ if (isArray(data)) {
1419
+ if (!isSignal(data[0])) {
1420
+ return data;
1421
+ }
1422
+ seen.set(data, data.map((item) => convertSignalToObject(isCloneDeep ? cloneDeep(item) : item, isCloneDeep, seen)));
1423
+ return seen.get(data);
1424
+ }
1425
+ if (isMap(data)) {
1426
+ const mapCopy = new Map();
1427
+ Array.from(data.keys()).forEach((key) => {
1428
+ mapCopy.set(key, convertSignalToObject(isCloneDeep ? cloneDeep(data.get(key)) : data.get(key), isCloneDeep));
1429
+ });
1430
+ seen.set(data, mapCopy);
1431
+ return seen.get(data);
1432
+ }
1433
+ if (isSet(data)) {
1434
+ const setCopy = new Set();
1435
+ Array.from(data.values()).forEach((value) => {
1436
+ setCopy.add(convertSignalToObject(isCloneDeep ? cloneDeep(value) : value, isCloneDeep));
1437
+ });
1438
+ seen.set(data, setCopy);
1439
+ return seen.get(data);
1440
+ }
1441
+ // Bỏ qua các đối tượng nguy hiểm và đặc biệt
1442
+ if (isReturnAsIsObject(data)) {
1443
+ return data;
1444
+ }
1445
+ const result = isCloneDeep ? {} : data;
1446
+ seen.set(data, result);
1447
+ for (const key in data) {
1448
+ const value = data[key];
1449
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
1450
+ // Bỏ qua các đối tượng nguy hiểm để tránh maximum call stack
1451
+ if (isReturnAsIsObject(value)) {
1452
+ result[key] = value;
1453
+ continue;
1454
+ }
1455
+ result[key] = convertSignalToObject(isCloneDeep ? cloneDeep(value) : value, isCloneDeep);
1456
+ }
1457
+ }
1458
+ return result;
1459
+ };
1460
+ const unwrapSignal = (data) => {
1461
+ while (isSignal(data)) {
1462
+ data = data();
1463
+ }
1464
+ return data;
1465
+ };
1466
+
1467
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1468
+ /**Các hàm tương tự thư viện lodash */
1469
+ /**
1470
+ * Kiểm tra xem một giá trị có phải là null hoặc undefined hay không
1471
+ * @param value Giá trị cần kiểm tra
1472
+ * @returns true nếu giá trị là null hoặc undefined, false nếu không
1473
+ * @example
1474
+ * isNil(null); // true
1475
+ * isNil(undefined); // true
1476
+ * isNil(0); // false
1477
+ * isNil('hello'); // false
1478
+ */
1479
+ const isNil = (value, options) => {
1480
+ if (!options?.ignoreUnWrapSignal) {
1481
+ value = unwrapSignal(value);
1482
+ }
1483
+ return value === null || value === undefined;
1484
+ };
1485
+ /**
1486
+ * Kiểm tra xem một giá trị có phải là rỗng hay không
1487
+ * @param value Giá trị cần kiểm tra
1488
+ * @returns true nếu giá trị là null, rỗng hoặc undefined, false nếu không
1489
+ * @example
1490
+ * isEmpty(null); // true
1491
+ * isEmpty(''); // true
1492
+ * isEmpty(undefined); // true
1493
+ * isEmpty({}); // true
1494
+ * isEmpty([]); // true
1495
+ * isEmpty([1, 2, 3]); // false
1496
+ * isEmpty({ a: 1 }); // false
1497
+ */
1498
+ const isEmpty = (value, options) => {
1499
+ if (!options?.ignoreUnWrapSignal) {
1500
+ value = unwrapSignal(value);
1501
+ }
1502
+ if (options?.ignoreCheckString && value === '') {
1503
+ return false;
1504
+ }
1505
+ if (options?.ignoreCheckTypePrimitive) {
1506
+ return typeof value === 'object' && isEmptyTypeObject(value);
1507
+ }
1508
+ return isEmptyTypeObject(value);
1509
+ };
1510
+ /**
1511
+ * Kiểm tra xem một giá trị có phải là object rỗng hay không
1512
+ * @param value Giá trị cần kiểm tra
1513
+ * @returns true nếu giá trị là object rỗng, false nếu không
1514
+ * @example
1515
+ * isEmptyTypeObject({}); // true
1516
+ * isEmptyTypeObject({ a: 1 }); // false
1517
+ */
1518
+ const isEmptyTypeObject = (value) => {
1519
+ try {
1520
+ if (isNil(value, { ignoreUnWrapSignal: true }) || value === '') {
1521
+ return true;
1522
+ }
1523
+ if (typeof value !== 'object') {
1524
+ return false;
1525
+ }
1526
+ if (Array.isArray(value)) {
1527
+ return value.length === 0;
1528
+ }
1529
+ return Object.keys(value).length === 0;
1530
+ }
1531
+ catch (error) {
1532
+ console.error(error);
1533
+ return false;
1534
+ }
1535
+ };
1536
+ /**
1537
+ * Kiểm tra xem một giá trị có phải là null, rỗng, undefined hoặc 0 hay không
1538
+ * @param value Giá trị cần kiểm tra
1539
+ * @param options Cấu hình tùy chọn
1540
+ * @param options.ignoreZero Nếu true, sẽ không kiểm tra giá trị 0
1541
+ * @returns true nếu giá trị là null, rỗng, undefined hoặc 0, false nếu không
1542
+ * @example
1543
+ * isTruthy(null); // false
1544
+ * isTruthy(''); // false
1545
+ * isTruthy(undefined); // false
1546
+ * isTruthy(0); // false
1547
+ * isTruthy({}); // true
1548
+ * isTruthy(0, { ignoreZero: true }); // true
1549
+ */
1550
+ const isTruthy = (value, options) => {
1551
+ return !isFalsy(value, options);
1552
+ };
1553
+ /**
1554
+ * Kiểm tra xem một giá trị có phải là null, rỗng, undefined hoặc 0 hay không
1555
+ * @param value Giá trị cần kiểm tra
1556
+ * @param options Cấu hình tùy chọn
1557
+ * @param options.ignoreZero Nếu true, sẽ không kiểm tra giá trị 0
1558
+ * @returns true nếu giá trị là null, rỗng, undefined hoặc 0, false nếu không
1559
+ * @example
1560
+ * isFalsy(null); // true
1561
+ * isFalsy(''); // true
1562
+ * isFalsy(undefined); // true
1563
+ * isFalsy(0); // true
1564
+ * isFalsy({}); // false
1565
+ * isFalsy(0, { ignoreZero: true }); // false
1566
+ */
1567
+ const isFalsy = (value, options) => {
1568
+ if (!options?.ignoreUnWrapSignal) {
1569
+ value = unwrapSignal(value);
1570
+ }
1571
+ if (options?.ignoreZero && value === 0) {
1572
+ return false;
1573
+ }
1574
+ if (options?.ignoreCheckString && value === '') {
1575
+ return false;
1576
+ }
1577
+ return !value;
1578
+ };
1579
+ /**
1580
+ * Loại bỏ các thuộc tính của đối tượng dựa trên một hàm điều kiện
1581
+ * @param objData Đối tượng cần xử lý
1582
+ * @param predicate Hàm điều kiện để kiểm tra giá trị của thuộc tính. Nếu hàm trả về true thì thuộc tính đó sẽ bị loại bỏ
1583
+ * @returns Đối tượng mới sau khi đã loại bỏ các thuộc tính thỏa mãn điều kiện
1584
+ * @example
1585
+ * const obj = { a: 1, b: null, c: 3, d: undefined };
1586
+ * omitBy(obj, isNil); // { a: 1, c: 3 }
1587
+ */
1588
+ const omitBy = (objData, predicate) => {
1589
+ if (!objData || typeof objData !== 'object' || Array.isArray(objData)) {
1590
+ return objData;
1591
+ }
1592
+ const newObj = {};
1593
+ Object.keys(objData).forEach((key) => {
1594
+ const valueOfKey = get(objData, key);
1595
+ if (!predicate(valueOfKey)) {
1596
+ set(newObj, key, valueOfKey);
1597
+ }
1598
+ });
1599
+ return newObj;
1600
+ };
1601
+ /**
1602
+ * Lấy giá trị từ đối tượng theo đường dẫn chỉ định
1603
+ *
1604
+ * Hàm này giúp bạn truy cập vào các thuộc tính sâu bên trong một đối tượng một cách an toàn,
1605
+ * tránh lỗi khi thuộc tính không tồn tại.
1606
+ *
1607
+ * @param obj Đối tượng nguồn cần lấy giá trị
1608
+ * @param path Đường dẫn đến thuộc tính cần lấy. Có thể là:
1609
+ * - Chuỗi: 'user.profile.name' hoặc 'items[0].title'
1610
+ * - Mảng: ['user', 'profile', 'name'] hoặc ['items', '0', 'title']
1611
+ * - Chuỗi rỗng '': trả về chính đối tượng gốc
1612
+ * @param defaultValue Giá trị mặc định trả về khi không tìm thấy thuộc tính (mặc định: undefined)
1613
+ * @param keepLastValueIfSignal Có giữ nguyên signal cuối cùng hay không (mặc định: false - sẽ gọi signal())
1614
+ * @returns Giá trị tìm được hoặc giá trị mặc định
1615
+ *
1616
+ * @example
1617
+ * // Ví dụ cơ bản
1618
+ * const user = { name: 'John', age: 30 };
1619
+ * get(user, 'name'); // 'John'
1620
+ * get(user, 'email'); // undefined
1621
+ * get(user, 'email', 'no-email'); // 'no-email'
1622
+ *
1623
+ * @example
1624
+ * // Truyền path rỗng - trả về chính đối tượng gốc
1625
+ * const data = { name: 'Alice', age: 25 };
1626
+ * get(data, ''); // { name: 'Alice', age: 25 } (chính đối tượng data)
1627
+ * get(data, '', 'default'); // { name: 'Alice', age: 25 } (bỏ qua defaultValue)
1628
+ *
1629
+ * @example
1630
+ * // Truy cập thuộc tính sâu
1631
+ * const data = {
1632
+ * user: {
1633
+ * profile: {
1634
+ * name: 'Alice',
1635
+ * settings: { theme: 'dark' }
1636
+ * }
1637
+ * }
1638
+ * };
1639
+ * get(data, 'user.profile.name'); // 'Alice'
1640
+ * get(data, 'user.profile.settings.theme'); // 'dark'
1641
+ * get(data, 'user.profile.avatar', 'default.jpg'); // 'default.jpg'
1642
+ *
1643
+ * @example
1644
+ * // Truy cập mảng
1645
+ * const items = [
1646
+ * { name: 'Item 1', price: 100 },
1647
+ * { name: 'Item 2', price: 200 }
1648
+ * ];
1649
+ * get(items, '[0].name'); // 'Item 1'
1650
+ * get(items, '[1].name'); // 'Item 2'
1651
+ * get(items, '[2].name', 'Not found'); // 'Not found'
1652
+ *
1653
+ * @example
1654
+ * // Sử dụng với mảng path
1655
+ * const nested = { a: { b: { c: 'deep value' } } };
1656
+ * get(nested, ['a', 'b', 'c']); // 'deep value'
1657
+ * get(nested, ['a', 'b', 'd'], 'default'); // 'default'
1658
+ *
1659
+ * @example
1660
+ * // Trường hợp đặc biệt
1661
+ * get(null, 'any.path'); // undefined
1662
+ * get(undefined, 'any.path', 'fallback'); // 'fallback'
1663
+ */
1664
+ const get = (obj, path, defaultValue, keepLastValueIfSignal) => {
1665
+ // helper cast để tránh lặp lại kiểu điều kiện ở các return
1666
+ const out = (v) => v;
1667
+ if (isNil(obj)) {
1668
+ return out(defaultValue);
1669
+ }
1670
+ obj = unwrapSignal(obj);
1671
+ if (path === '') {
1672
+ return out(obj);
1673
+ }
1674
+ if (obj instanceof HttpParams) {
1675
+ return out(obj.get(`${path}`) ?? defaultValue);
1676
+ }
1677
+ if (obj instanceof DOMRect) {
1678
+ return out(obj[path]);
1679
+ }
1680
+ const paths = Array.isArray(path)
1681
+ ? path
1682
+ : path
1683
+ .replace(/\[(\d+)]/g, '.$1')
1684
+ .split('.')
1685
+ .filter((key) => key);
1686
+ for (const index in paths) {
1687
+ const key = paths[index];
1688
+ if (obj instanceof CSSStyleDeclaration) {
1689
+ obj = obj[key];
1690
+ continue;
1691
+ }
1692
+ if (obj instanceof HTMLElement) {
1693
+ obj = obj[key];
1694
+ continue;
1695
+ }
1696
+ if (isNil(obj) || !Object.prototype.hasOwnProperty.call(obj, key)) {
1697
+ return out(defaultValue);
1698
+ }
1699
+ const val = isSignal(obj[key]) && !keepLastValueIfSignal ? obj[key]() : obj[key];
1700
+ obj = val;
1701
+ }
1702
+ return out(obj ?? defaultValue);
1703
+ };
1704
+ /**
1705
+ * Thiết lập giá trị cho một thuộc tính trong đối tượng theo đường dẫn chỉ định
1706
+ * @param obj Đối tượng cần thiết lập giá trị
1707
+ * @param path Đường dẫn đến thuộc tính, có thể là chuỗi (vd: 'a.b.c') hoặc mảng (vd: ['a', 'b', 'c'])
1708
+ * @param value Giá trị cần thiết lập
1709
+ * @returns Đối tượng sau khi đã thiết lập giá trị
1710
+ * @throws Error nếu tham số đầu tiên không phải là đối tượng
1711
+ * @example
1712
+ * const obj = { a: { b: 1 } };
1713
+ * set(obj, 'a.b', 2); // { a: { b: 2 } }
1714
+ */
1715
+ const set = (obj, path, value, options) => {
1716
+ if (!obj || (typeof obj !== 'object' && !isSignal(obj)) || (isSignal(obj) && typeof obj() !== 'object')) {
1717
+ throw new Error('The first argument must be an object');
1718
+ }
1719
+ if (obj instanceof HttpParams) {
1720
+ return obj.set(`${path}`, value);
1721
+ }
1722
+ const pathArray = Array.isArray(path)
1723
+ ? path
1724
+ : path
1725
+ .replace(/\[(\d+)]/g, '.[$1]')
1726
+ .split('.')
1727
+ .filter((key) => key);
1728
+ let currentObjectByKey = isSignal(obj) ? obj() : obj;
1729
+ let preObjectByKey = obj;
1730
+ pathArray.forEach((key, index) => {
1731
+ if (index < pathArray.length - 1) {
1732
+ if (!(key in currentObjectByKey) || (typeof currentObjectByKey[key] !== 'object' && !isSignal(currentObjectByKey[key]))) {
1733
+ const nextKey = pathArray[index + 1];
1734
+ key = key.replace(/\[(\d+)]/g, '$1');
1735
+ if (isNil(currentObjectByKey[key])) {
1736
+ currentObjectByKey[key] = /\[(\d+)]/g.test(nextKey) ? (options?.valueDefaultPathArrayUndefined ?? []) : (options?.valueDefaultPathObjectUndefined ?? {});
1737
+ }
1738
+ }
1739
+ currentObjectByKey = key ? currentObjectByKey[key] : currentObjectByKey;
1740
+ preObjectByKey = currentObjectByKey;
1741
+ currentObjectByKey = isSignal(currentObjectByKey) ? currentObjectByKey() : currentObjectByKey;
1742
+ return;
1743
+ }
1744
+ if (typeof currentObjectByKey !== 'object') {
1745
+ return;
1746
+ }
1747
+ // Gán giá trị ở cuối đường dẫn
1748
+ key = key.replace(/\[(\d+)]/g, '$1');
1749
+ const valueOfKey = currentObjectByKey[key];
1750
+ const valueOfKeyIsSignal = isSignal(valueOfKey);
1751
+ if (valueOfKeyIsSignal && (typeof valueOfKey() !== 'object' || valueOfKey() === null)) {
1752
+ valueOfKey.set(isSignal(value) ? value() : value);
1753
+ return;
1754
+ }
1755
+ if (isSignal(preObjectByKey)) {
1756
+ preObjectByKey.update((data) => {
1757
+ const dataOfKey = data[key];
1758
+ if (isNil(dataOfKey) || !valueOfKeyIsSignal) {
1759
+ data[key] = value;
1760
+ }
1761
+ if (valueOfKeyIsSignal) {
1762
+ valueOfKey.set(isSignal(value) ? value() : value);
1763
+ }
1764
+ if (Array.isArray(data)) {
1765
+ return [...data];
1766
+ }
1767
+ return { ...data };
1768
+ });
1769
+ return;
1770
+ }
1771
+ if (valueOfKeyIsSignal) {
1772
+ valueOfKey.set(isSignal(value) ? value() : value);
1773
+ return;
1774
+ }
1775
+ currentObjectByKey[key] = value;
1776
+ });
1777
+ return obj;
1778
+ };
1779
+ /**
1780
+ * Tạo một bản sao sâu của một đối tượng hoặc giá trị bất kỳ
1781
+ * @param data Dữ liệu cần sao chép
1782
+ * @param options Tùy chọn cấu hình
1783
+ * @param options.ignoreSignal Nếu true, sẽ không sao chép các signal mà trả về signal gốc
1784
+ * @param seen WeakMap để theo dõi các tham chiếu đã được sao chép, tránh lặp vô hạn với các tham chiếu vòng
1785
+ * @returns Bản sao sâu của dữ liệu đầu vào
1786
+ * @example
1787
+ * const obj = {
1788
+ * a: 1,
1789
+ * b: { c: 2 },
1790
+ * d: [1, 2, 3]
1791
+ * };
1792
+ * const clone = cloneDeep(obj);
1793
+ * // clone là một bản sao hoàn toàn độc lập của obj
1794
+ */
1795
+ const cloneDeep = (data, options = { ignoreSignal: false }, seen = new WeakMap()) => {
1796
+ if (data === null || (typeof data !== 'object' && !isSignal(data)) || isDangerousObject(data)) {
1797
+ return data;
1798
+ }
1799
+ if (seen.has(data)) {
1800
+ return seen.get(data);
1801
+ }
1802
+ if (data instanceof HttpParams) {
1803
+ return new UtilsHttpParamsRequest(undefined, data);
1804
+ }
1805
+ if (data instanceof Date) {
1806
+ return new Date(data.getTime());
1807
+ }
1808
+ if (isDayjsObject(data)) {
1809
+ return getDayjs({ date: data.valueOf() });
1810
+ }
1811
+ if (isRegExp(data)) {
1812
+ return new RegExp(data.source, data.flags);
1813
+ }
1814
+ if (isMap(data)) {
1815
+ const mapCopy = new Map();
1816
+ seen.set(data, mapCopy);
1817
+ data.forEach((val, key) => {
1818
+ mapCopy.set(cloneDeep(key, options, seen), cloneDeep(val, options, seen));
1819
+ });
1820
+ return mapCopy;
1821
+ }
1822
+ if (isSet(data)) {
1823
+ const setCopy = new Set();
1824
+ seen.set(data, setCopy);
1825
+ data.forEach((val) => {
1826
+ setCopy.add(cloneDeep(val, options, seen));
1827
+ });
1828
+ return setCopy;
1829
+ }
1830
+ if (isArray(data)) {
1831
+ seen.set(data, data.map((item) => cloneDeep(item, options, seen)));
1832
+ return seen.get(data);
1833
+ }
1834
+ if (isReturnAsIsObject(data)) {
1835
+ return data;
1836
+ }
1837
+ if (isSignal(data)) {
1838
+ if (options?.ignoreSignal) {
1839
+ return data;
1840
+ }
1841
+ seen.set(data, signal(cloneDeep(data(), options, seen)));
1842
+ return seen.get(data);
1843
+ }
1844
+ const result = {};
1845
+ seen.set(data, result);
1846
+ for (const key in data) {
1847
+ const value = data[key];
1848
+ if (value instanceof HttpParams) {
1849
+ result[key] = new UtilsHttpParamsRequest(undefined, value);
1850
+ continue;
1851
+ }
1852
+ if (value instanceof Date) {
1853
+ result[key] = new Date(value.getTime());
1854
+ continue;
1855
+ }
1856
+ if (dayjs.isDayjs(value)) {
1857
+ result[key] = getDayjs({ date: value.valueOf() });
1858
+ continue;
1859
+ }
1860
+ if (isRegExp(data)) {
1861
+ result[key] = new RegExp(value.source, value.flags);
1862
+ continue;
1863
+ }
1864
+ if (isReturnAsIsObject(data)) {
1865
+ result[key] = value;
1866
+ continue;
1867
+ }
1868
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
1869
+ result[key] = cloneDeep(value, options, seen);
1870
+ }
1871
+ }
1872
+ return result;
1873
+ };
1874
+ /**
1875
+ * Chuyển đổi một mảng các đối tượng thành một đối tượng với khóa được chỉ định
1876
+ * @param data Mảng các đối tượng cần chuyển đổi
1877
+ * @param key Tên thuộc tính được sử dụng làm khóa trong đối tượng kết quả
1878
+ * @returns Đối tượng với các giá trị từ mảng được đánh key theo thuộc tính đã chỉ định
1879
+ * @example
1880
+ * const data = [
1881
+ * { id: 1, name: 'John' },
1882
+ * { id: 2, name: 'Jane' }
1883
+ * ];
1884
+ * keyBy(data, 'id');
1885
+ * // Kết quả: {
1886
+ * // '1': { id: 1, name: 'John' },
1887
+ * // '2': { id: 2, name: 'Jane' }
1888
+ * // }
1889
+ */
1890
+ const keyBy = (data, key) => {
1891
+ if (!data || !data.length || !key) {
1892
+ return {};
1893
+ }
1894
+ return data.reduce((dir, nextItem) => {
1895
+ const valueOfKey = get(nextItem, key);
1896
+ if (typeof valueOfKey !== 'string' && typeof valueOfKey !== 'number' && typeof valueOfKey !== 'boolean') {
1897
+ return dir;
1898
+ }
1899
+ if (!Object.keys(dir).includes(`${valueOfKey}`)) {
1900
+ dir[`${valueOfKey}`] = nextItem;
1901
+ }
1902
+ return dir;
1903
+ }, {});
1904
+ };
1905
+ /**
1906
+ * Nhóm các đối tượng trong một mảng thành các nhóm dựa trên một thuộc tính cụ thể
1907
+ * @param data Mảng các đối tượng cần nhóm
1908
+ * @param key Tên thuộc tính được sử dụng làm khóa nhóm
1909
+ * @returns Đối tượng với các giá trị từ mảng được nhóm theo thuộc tính đã chỉ định
1910
+ * @example
1911
+ * const data = [
1912
+ * { id: 1, name: 'John' },
1913
+ * { id: 2, name: 'Jane' },
1914
+ * { id: 1, name: 'John' }
1915
+ * ];
1916
+ * groupBy(data, 'id');
1917
+ * // Kết quả: {
1918
+ * // '1': [
1919
+ * // { id: 1, name: 'John' },
1920
+ * // { id: 1, name: 'John' }
1921
+ * // ],
1922
+ * // '2': [
1923
+ * // { id: 2, name: 'Jane' }
1924
+ * // }
1925
+ */
1926
+ const groupBy = (data, key) => {
1927
+ if (!data || !data.length || !Object.keys(get(data, '[0]')).length || !key) {
1928
+ return {};
1929
+ }
1930
+ return data.reduce((dir, nextItem) => {
1931
+ const valueOfKey = get(nextItem, key);
1932
+ if (typeof valueOfKey !== 'string' && typeof valueOfKey !== 'number' && typeof valueOfKey !== 'boolean') {
1933
+ return dir;
1934
+ }
1935
+ if (!Object.keys(dir).includes(`${valueOfKey}`)) {
1936
+ dir[`${valueOfKey}`] = [];
1937
+ }
1938
+ dir[`${valueOfKey}`].push(nextItem);
1939
+ return dir;
1940
+ }, {});
1941
+ };
1942
+ /**
1943
+ * Tạo một mảng các số từ giá trị bắt đầu đến giá trị kết thúc với bước nhảy tùy chọn
1944
+ * @param start Giá trị bắt đầu của dãy số. Nếu chỉ có một tham số, đây sẽ là giá trị kết thúc và giá trị bắt đầu sẽ là 0
1945
+ * @param end Giá trị kết thúc của dãy số (tùy chọn)
1946
+ * @param step Bước nhảy giữa các số trong dãy (tùy chọn). Mặc định là 1 nếu end > start, -1 nếu end < start
1947
+ * @returns Mảng các số từ start đến end với bước nhảy step
1948
+ * @example
1949
+ * range(4); // [0, 1, 2, 3]
1950
+ * range(1, 5); // [1, 2, 3, 4]
1951
+ * range(0, 20, 5); // [0, 5, 10, 15]
1952
+ * range(5, 2); // [5, 4, 3]
1953
+ */
1954
+ const range = (start, end, step) => {
1955
+ if (end === undefined || end === null) {
1956
+ end = start;
1957
+ start = 0;
1958
+ }
1959
+ if (!step) {
1960
+ step = end < 0 ? -1 : 1;
1961
+ }
1962
+ if (end < start && step > 0) {
1963
+ step *= -1;
1964
+ }
1965
+ const valueStartByStep = step + start;
1966
+ const direction = start < end ? 'asc' : 'desc';
1967
+ if ((direction === 'asc' && (valueStartByStep < start || valueStartByStep > end)) || (direction === 'desc' && (valueStartByStep > start || valueStartByStep < end))) {
1968
+ return [start];
1969
+ }
1970
+ const arr = new Array();
1971
+ for (let index = 0; index < Math.abs(end - start); index++) {
1972
+ let value = start + index * step;
1973
+ if (index === 0) {
1974
+ value = start;
1975
+ }
1976
+ if ((direction === 'asc' && (value < start || value > end)) || (direction === 'desc' && (value > start || value < end))) {
1977
+ return arr;
1978
+ }
1979
+ arr.push(value);
1980
+ }
1981
+ return arr;
1982
+ };
1983
+ /**
1984
+ * So sánh hai giá trị bất kỳ có bằng nhau hay không
1985
+ * @param value1 Giá trị thứ nhất cần so sánh
1986
+ * @param value2 Giá trị thứ hai cần so sánh
1987
+ * @param exactlyPosition Có so sánh chính xác vị trí các phần tử trong mảng hay không
1988
+ * @returns true nếu hai giá trị bằng nhau, false nếu không bằng nhau
1989
+ * @example
1990
+ * isEqual([1,2,3], [1,2,3]); // true
1991
+ * isEqual([1,2,3], [3,2,1]); // true khi exactlyPosition = false
1992
+ * isEqual([1,2,3], [3,2,1]); // false khi exactlyPosition = true
1993
+ * isEqual({a:1}, {a:1}); // true
1994
+ */
1995
+ const isEqual = (value1, value2, options) => {
1996
+ // Handle signals
1997
+ if (!options?.ignoreUnWrapSignal) {
1998
+ value1 = unwrapSignal(value1);
1999
+ value2 = unwrapSignal(value2);
2000
+ }
2001
+ const { exactlyPosition = false, ignoreExactlyDataType = false } = options || {};
2002
+ if (value1 === value2 || (value1 === null && value2 === null) || (value1 === undefined && value2 === undefined)) {
2003
+ return true;
2004
+ }
2005
+ if (ignoreExactlyDataType) {
2006
+ return isEqual(isNil(value1) ? undefined : `${value1}`, isNil(value2) ? undefined : `${value2}`);
2007
+ }
2008
+ if (typeof value1 !== 'object' || typeof value2 !== 'object' || (Array.isArray(value1) && !Array.isArray(value2)) || (!Array.isArray(value1) && Array.isArray(value2))) {
2009
+ return false;
2010
+ }
2011
+ if (Array.isArray(value1) || Array.isArray(value2)) {
2012
+ if (value1.length !== value2.length) {
2013
+ return false;
2014
+ }
2015
+ if (!exactlyPosition) {
2016
+ return !value1.some((item) => !value2.includes(item));
2017
+ }
2018
+ return !value1.some((item, index) => !isEqual(item, value2[index], options));
2019
+ }
2020
+ if (Object.keys(value1).length !== Object.keys(value2).length) {
2021
+ return false;
2022
+ }
2023
+ return !Object.keys(value1).some((key) => !isEqual(value1[key], value2[key], options));
2024
+ };
2025
+ /**
2026
+ * Loại bỏ các phần tử trùng lặp trong mảng dựa trên một thuộc tính chỉ định
2027
+ * @param data Mảng dữ liệu cần xử lý
2028
+ * @param key Tên thuộc tính dùng để so sánh trùng lặp. Nếu không có key thì so sánh trực tiếp giá trị
2029
+ * @returns Mảng mới chứa các phần tử không trùng lặp
2030
+ * @example
2031
+ * const arr = [
2032
+ * { id: 1, name: 'A' },
2033
+ * { id: 2, name: 'B' },
2034
+ * { id: 1, name: 'C' }
2035
+ * ];
2036
+ * uniqBy(arr, 'id'); // [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
2037
+ *
2038
+ * const numbers = [1, 2, 2, 3, 3];
2039
+ * uniqBy(numbers); // [1, 2, 3]
2040
+ *
2041
+ * const numbersSignal = [signal(1), signal(2), signal(3), signal(2), signal(5), signal(4), signal(1), signal(6), signal(7), signal(6)];
2042
+ * uniqBy(numbersSignal); // [signal(1), signal(2), signal(3), signal(5), signal(4), signal(6), signal(7)]
2043
+ */
2044
+ const uniqBy = (data, key) => {
2045
+ if (!key || !data?.length || typeof get(data, '[0]') !== 'object') {
2046
+ // Xử lý mảng chứa signal values
2047
+ if (data[0] && isSignal(data[0])) {
2048
+ const seen = new Set();
2049
+ return data.filter((item) => {
2050
+ const value = `${get(item, '')}`;
2051
+ if (seen.has(value)) {
2052
+ return false;
2053
+ }
2054
+ seen.add(value);
2055
+ return true;
2056
+ });
2057
+ }
2058
+ // Xử lý mảng primitive values
2059
+ return Array.from(new Set(data));
2060
+ }
2061
+ const dataUnique = keyBy(data, key);
2062
+ return Object.keys(dataUnique).map((key) => dataUnique[key]);
2063
+ };
2064
+ const generateInterface = (obj, interfaceName) => {
2065
+ const generateType = (value) => {
2066
+ if (value === null) {
2067
+ return 'null';
2068
+ }
2069
+ const type = typeof value;
2070
+ if (type === 'string') {
2071
+ return 'string';
2072
+ }
2073
+ if (type === 'number') {
2074
+ return 'number';
2075
+ }
2076
+ if (type === 'boolean') {
2077
+ return 'boolean';
2078
+ }
2079
+ if (type === 'undefined') {
2080
+ return 'any';
2081
+ }
2082
+ if (value instanceof Date) {
2083
+ return 'Date';
2084
+ }
2085
+ if (value instanceof RegExp) {
2086
+ return 'RegExp';
2087
+ }
2088
+ if (Array.isArray(value)) {
2089
+ if (value.length === 0) {
2090
+ return 'Array<any>';
2091
+ }
2092
+ return `Array<${generateType(value[0])}>`;
2093
+ }
2094
+ if (type === 'object') {
2095
+ let interfaceStr = '{\n';
2096
+ for (const key in value) {
2097
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
2098
+ const valueType = generateType(value[key]);
2099
+ interfaceStr += ` ${key}: ${valueType};\n`;
2100
+ }
2101
+ }
2102
+ interfaceStr += '}';
2103
+ return interfaceStr;
2104
+ }
2105
+ return 'any';
2106
+ };
2107
+ const interfaceStr = `interface ${interfaceName} ${generateType(obj)}`;
2108
+ return interfaceStr;
2109
+ };
2110
+
2111
+ const deviceDetector = new DeviceDetector();
2112
+ const parser = new DOMParser();
2113
+ const quill = new Quill(document.createElement('div'));
2114
+ const getDeviceInfo = () => {
2115
+ return deviceDetector.parse(window.navigator.userAgent);
2116
+ };
2117
+ const getPlatFromBrowser = () => {
2118
+ const info = getDeviceInfo();
2119
+ return `${info.client?.name} ${info.client?.version}`;
2120
+ };
2121
+ const getEventNameHandleClick = (() => {
2122
+ const deviceInfo = getDeviceInfo();
2123
+ let nameEventHandleClick = deviceInfo.device?.type === 'desktop' ? 'click' : 'touchstart';
2124
+ if ('onpointerdown' in window) {
2125
+ nameEventHandleClick = 'pointerdown';
2126
+ }
2127
+ return nameEventHandleClick;
2128
+ })();
2129
+ const getDocumentByString = (htmlStr) => {
2130
+ return parser.parseFromString(htmlStr, 'text/html');
2131
+ };
2132
+ const cloneIBoundingClientRect = (rect) => {
2133
+ return {
2134
+ top: rect['top'],
2135
+ left: rect['left'],
2136
+ width: rect['width'],
2137
+ height: rect['height'],
2138
+ bottom: rect['bottom'],
2139
+ };
2140
+ };
2141
+ const setStylesElement = (el, styles) => {
2142
+ Object.keys(styles).forEach((key) => set(el, `style.${key}`, styles[key]));
2143
+ };
2144
+ const getViewport = (win = window) => {
2145
+ const doc = win.document.documentElement, body = win.document.getElementsByTagName('body')[0], w = win.innerWidth || doc.clientWidth || body.clientWidth, h = win.innerHeight || doc.clientHeight || body.clientHeight;
2146
+ return { width: w, height: h };
2147
+ };
2148
+ const setCaretPosition = (element, position) => {
2149
+ if (!element || !element.setSelectionRange) {
2150
+ return;
2151
+ }
2152
+ element.focus();
2153
+ element.setSelectionRange(position, position);
2154
+ };
2155
+ const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
2156
+ if (!container || !element) {
2157
+ return false;
2158
+ }
2159
+ const rectContainer = cloneIBoundingClientRect(container.getBoundingClientRect());
2160
+ if (elementScroll) {
2161
+ const rectElementScroll = elementScroll.getBoundingClientRect();
2162
+ rectContainer['left'] = rectElementScroll.left;
2163
+ rectContainer['top'] = rectElementScroll.top;
2164
+ }
2165
+ const rectElement = element.getBoundingClientRect();
2166
+ const { left, top } = rectElement;
2167
+ const topContainer = rectContainer['top'];
2168
+ const leftContainer = rectContainer['left'];
2169
+ const widthContainer = rectContainer['width'];
2170
+ const heightContainer = rectContainer['height'];
2171
+ const scrollTopContainer = container.scrollTop;
2172
+ const scrollLeftContainer = container.scrollLeft;
2173
+ const limitTop = topContainer + scrollTopContainer + heightContainer;
2174
+ const limitLeft = leftContainer + scrollLeftContainer + widthContainer;
2175
+ const maxTopItem = top + (maxTopLeft?.top || 0);
2176
+ const maxLeftItem = left + (maxTopLeft?.left || 0);
2177
+ if (topContainer <= maxTopItem && maxTopItem <= limitTop && leftContainer <= maxLeftItem && maxLeftItem <= limitLeft) {
2178
+ return true;
2179
+ }
2180
+ return false;
2181
+ };
2182
+ const checkMouseOverInContainer = (mousePosition, element, rect) => {
2183
+ if (!element && !rect) {
2184
+ return false;
2185
+ }
2186
+ const rectElement = rect || element?.getBoundingClientRect() || {};
2187
+ const { left, top, width, height } = rectElement;
2188
+ const limitLeft = left + width;
2189
+ const limitTop = top + height;
2190
+ const { clientX, clientY } = mousePosition;
2191
+ if (left <= clientX && clientX <= limitLeft && top <= clientY && clientY <= limitTop) {
2192
+ return true;
2193
+ }
2194
+ return false;
2195
+ };
2196
+ const getDragEventByElement = (config) => {
2197
+ let timer;
2198
+ const addClass = () => {
2199
+ timer = setTimeout(() => {
2200
+ document.body.classList.add('!select-none');
2201
+ document.body.classList.add('!cursor-grabbing');
2202
+ }, 250);
2203
+ };
2204
+ const removeClass = () => {
2205
+ document.body.classList.remove('!select-none');
2206
+ document.body.classList.remove('!cursor-grabbing');
2207
+ clearTimeout(timer);
2208
+ };
2209
+ fromEvent(config.elementMouseDown, 'mouseleave')
2210
+ .pipe(tap(() => document.body.classList.remove('cursor-pointer')), takeUntil(config.onDestroy))
2211
+ .subscribe();
2212
+ fromEvent(config.elementMouseDown, 'mouseenter')
2213
+ .pipe(tap(() => document.body.classList.add('cursor-pointer')), takeUntil(config.onDestroy))
2214
+ .subscribe();
2215
+ const mouseDown = fromEvent(config.elementMouseDown, 'mousedown').pipe(tap((e) => {
2216
+ e.stopPropagation();
2217
+ addClass();
2218
+ config.functionMouseDown?.(e);
2219
+ }));
2220
+ const mouseup = fromEvent(config.elementMouseUp || document, 'mouseup').pipe(tap((e) => {
2221
+ e.stopPropagation();
2222
+ removeClass();
2223
+ config.functionMouseUp?.(e);
2224
+ }));
2225
+ const mousemove = fromEvent(config.elementMouseMove || document, 'mousemove').pipe(tap((e) => {
2226
+ e.stopPropagation();
2227
+ config.functionMouseMove?.(e);
2228
+ }), takeUntil(mouseup));
2229
+ return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntil(config.onDestroy), finalize(removeClass));
2230
+ };
2231
+ const getHTMLFromQuill = async (data, options) => {
2232
+ const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
2233
+ const delta = typeof data === 'string' ? await getDeltaFromHTML(data) : data;
2234
+ if (options?.functionReplaceDelta) {
2235
+ options.functionReplaceDelta(delta);
2236
+ }
2237
+ delta.ops.forEach((op) => {
2238
+ if (op.insert) {
2239
+ if (typeof op.insert === 'string') {
2240
+ if (replaceNewLineTo) {
2241
+ op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
2242
+ }
2243
+ if (replaceTagBRTo) {
2244
+ op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
2245
+ }
2246
+ if (replaceTags?.length) {
2247
+ for (const tag of replaceTags) {
2248
+ op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
2249
+ op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
2250
+ }
2251
+ }
2252
+ }
2253
+ }
2254
+ });
2255
+ quill.setContents(delta);
2256
+ let htmlText = options?.getRootHtml ? quill.root.innerHTML : quill.root.firstElementChild?.innerHTML;
2257
+ if (replaceBrToDiv) {
2258
+ htmlText = convertHtmlToDivBlocks(htmlText || '');
2259
+ }
2260
+ return decodeEscapeHtml(htmlText || '');
2261
+ };
2262
+ const convertHtmlToDivBlocks = (html) => {
2263
+ const BREAK_TOKEN = '<<<BREAK>>>';
2264
+ // Bước 1: thay <br> thành token tạm
2265
+ const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
2266
+ // Bước 2: tách theo token
2267
+ const parts = normalizedHtml.split(BREAK_TOKEN);
2268
+ const parser = new DOMParser();
2269
+ const divs = [];
2270
+ for (const raw of parts) {
2271
+ const trimmed = raw.trim();
2272
+ if (!trimmed)
2273
+ continue;
2274
+ // parse mỗi phần nhỏ như một document riêng
2275
+ const doc = parser.parseFromString(trimmed, 'text/html');
2276
+ const body = doc.body;
2277
+ // Lấy lại nội dung bên trong body
2278
+ divs.push(`<div>${body.innerHTML}</div>`);
2279
+ }
2280
+ return divs.join('');
2281
+ };
2282
+ const getDeltaFromHTML = async (html) => {
2283
+ quill.root.innerHTML = html;
2284
+ setTimeout(() => {
2285
+ console.log(quill.getContents());
2286
+ }, 1000);
2287
+ await lastValueFrom(timer(1000));
2288
+ return quill.getContents();
2289
+ };
2290
+ const processPasteData = async (e, config) => {
2291
+ const element = config.element;
2292
+ const files = e.clipboardData?.files;
2293
+ if (files?.length) {
2294
+ e.preventDefault();
2295
+ config.handlerPasteFile?.(files);
2296
+ config.callBack?.('file');
2297
+ return;
2298
+ }
2299
+ // Lưu selection TRƯỚC khi prevent default
2300
+ const selection = window.getSelection();
2301
+ let savedRange = null;
2302
+ if (selection && selection.rangeCount > 0) {
2303
+ const range = selection.getRangeAt(0);
2304
+ // Chỉ lưu nếu range nằm trong contentText element
2305
+ const container = range.commonAncestorContainer;
2306
+ const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
2307
+ if (isInContentElement) {
2308
+ savedRange = range.cloneRange();
2309
+ }
2310
+ }
2311
+ // Prevent default để tự xử lý paste
2312
+ e.preventDefault();
2313
+ // Sử dụng Quill để clean HTML content
2314
+ const htmlContent = e.clipboardData?.getData('text/html') || '';
2315
+ const plainText = e.clipboardData?.getData('text/plain') || '';
2316
+ let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
2317
+ if (htmlContent) {
2318
+ contentToInsert = await getHTMLFromQuill(htmlContent);
2319
+ }
2320
+ if (!contentToInsert) {
2321
+ config.callBack?.('no-content');
2322
+ return;
2323
+ }
2324
+ if (savedRange) {
2325
+ insertContentWithRange(contentToInsert, savedRange, element);
2326
+ config.callBack?.('range');
2327
+ return;
2328
+ }
2329
+ element.innerHTML += contentToInsert;
2330
+ config.callBack?.('content');
2331
+ };
2332
+ const insertContentWithRange = (content, savedRange, element) => {
2333
+ const selection = window.getSelection();
2334
+ if (!selection) {
2335
+ // Fallback: append vào cuối
2336
+ element.innerHTML += content;
2337
+ return;
2338
+ }
2339
+ // Restore selection
2340
+ selection.removeAllRanges();
2341
+ selection.addRange(savedRange);
2342
+ // Xóa nội dung đã select (nếu có)
2343
+ savedRange.deleteContents();
2344
+ // Tạo document fragment từ HTML content
2345
+ const tempDiv = document.createElement('div');
2346
+ tempDiv.innerHTML = content;
2347
+ const fragment = document.createDocumentFragment();
2348
+ while (tempDiv.firstChild) {
2349
+ fragment.appendChild(tempDiv.firstChild);
2350
+ }
2351
+ // Insert fragment tại vị trí range
2352
+ savedRange.insertNode(fragment);
2353
+ // Di chuyển cursor đến cuối nội dung vừa insert
2354
+ if (fragment.lastChild) {
2355
+ savedRange.setStartAfter(fragment.lastChild);
2356
+ }
2357
+ savedRange.collapse(true);
2358
+ selection.removeAllRanges();
2359
+ selection.addRange(savedRange);
2360
+ };
2361
+
2362
+ class UtilsUrlSearchParams {
2363
+ static instance;
2364
+ params;
2365
+ constructor(paramString) {
2366
+ this.params = new Array();
2367
+ this.params.length = 0;
2368
+ this.params.push(...this.buildParams(paramString));
2369
+ }
2370
+ static getInstance() {
2371
+ if (!this.instance) {
2372
+ this.instance = new UtilsUrlSearchParams('');
2373
+ }
2374
+ return this.instance;
2375
+ }
2376
+ static toStringParamObject(params) {
2377
+ const paramsArr = Object.keys(params).map((key) => {
2378
+ return {
2379
+ key,
2380
+ value: params[key],
2381
+ };
2382
+ });
2383
+ return UtilsUrlSearchParams.ToString(paramsArr);
2384
+ }
2385
+ static ToString(params) {
2386
+ let paramsStr = '';
2387
+ params
2388
+ .sort((item1, item2) => item1.key.localeCompare(item2.key))
2389
+ .forEach((item) => {
2390
+ const { key, value } = item;
2391
+ paramsStr = `${paramsStr}${paramsStr ? '&' : ''}${key}=${value}`;
2392
+ });
2393
+ return paramsStr;
2394
+ }
2395
+ buildParams(paramString) {
2396
+ const paramsBuild = new Array();
2397
+ if (!paramString) {
2398
+ return paramsBuild;
2399
+ }
2400
+ const paramsError = paramString.split('?');
2401
+ if (paramsError.length > 2) {
2402
+ paramString = `${paramsError[0]}?${paramsError[1]}&${paramsError.slice(2).join('&')}`;
2403
+ }
2404
+ const params = paramString.replace(/\S+[?]/g, '').split('&');
2405
+ if (params) {
2406
+ params.forEach((param) => {
2407
+ if (param.indexOf('=') < 0) {
2408
+ return;
2409
+ }
2410
+ let [key, value] = param.split('=');
2411
+ key = key.replace(/[?&]+/g, '');
2412
+ value = decodeURIComponent(value.replace(/\+/g, ' '));
2413
+ if (!paramsBuild.some((param) => param.key === key)) {
2414
+ paramsBuild.push({ key, value });
2415
+ }
2416
+ });
2417
+ }
2418
+ return paramsBuild;
2419
+ }
2420
+ set(key, value) {
2421
+ const paramByKey = this.params.find((param) => param.key === key);
2422
+ if (!paramByKey) {
2423
+ this.params.push({ key, value: decodeURIComponent(value.replace(/\+/g, ' ')) });
2424
+ return;
2425
+ }
2426
+ paramByKey['value'] = value;
2427
+ }
2428
+ get(key) {
2429
+ return this.params.find((param) => param.key === key)?.value;
2430
+ }
2431
+ has(key) {
2432
+ return this.params.some((param) => param.key === key);
2433
+ }
2434
+ delete(key) {
2435
+ const index = this.params.findIndex((param) => param.key === key);
2436
+ if (index >= 0) {
2437
+ this.params.splice(index, 1);
2438
+ }
2439
+ }
2440
+ length() {
2441
+ return this.params.length;
2442
+ }
2443
+ toString(params) {
2444
+ params = params || this.params;
2445
+ return UtilsUrlSearchParams.ToString(params);
2446
+ }
2447
+ getParamObject() {
2448
+ const params = {};
2449
+ this.params.forEach((item) => {
2450
+ params[item.key] = item.value;
2451
+ });
2452
+ return params;
2453
+ }
2454
+ compareParams(param1, param2) {
2455
+ const params1 = this.toString(this.buildParams(param1));
2456
+ const params2 = this.toString(this.buildParams(param2));
2457
+ return params1 === params2;
2458
+ }
2459
+ }
2460
+
2461
+ const step = 20;
2462
+ const percent = 0.05;
2463
+ const colorStepContrastFromOrigin = (color, stepNumber) => {
2464
+ return colorContrastFromOrigin(color).find((item) => item.step === stepNumber);
2465
+ };
2466
+ const colorContrastFromOrigin = (color) => {
2467
+ const parsedColorsArray = parseColorValues(color);
2468
+ const colors = [];
2469
+ let calculatedShades = [];
2470
+ let calculatedTints = [];
2471
+ if (parsedColorsArray !== null) {
2472
+ for (let i = 0; i < parsedColorsArray.length; i++) {
2473
+ calculatedShades = calculateShades(parsedColorsArray[i]);
2474
+ calculatedTints = calculateTints(parsedColorsArray[i]);
2475
+ }
2476
+ for (let i = 0; i <= step; i++) {
2477
+ colors.push({ step: i * 5, label: `${i * 5}%`, dark: `#${calculatedShades[i]}`, light: `#${calculatedTints[i]}` });
2478
+ }
2479
+ }
2480
+ return colors;
2481
+ };
2482
+ const parseColorValues = (colorValues) => {
2483
+ let colorValuesArray = colorValues.match(/\b[0-9A-Fa-f]{3}\b|[0-9A-Fa-f]{6}\b/g);
2484
+ if (colorValuesArray) {
2485
+ colorValuesArray = colorValuesArray.map((item) => {
2486
+ if (item.length === 3) {
2487
+ let newItem = item.toString().split('');
2488
+ newItem = newItem.reduce((acc, it) => {
2489
+ return acc + it + it;
2490
+ }, '');
2491
+ return newItem;
2492
+ }
2493
+ return item;
2494
+ });
2495
+ }
2496
+ return colorValuesArray;
2497
+ };
2498
+ const calculateShades = (colorValue) => {
2499
+ return calculate(colorValue, rgbShade).concat('000000');
2500
+ };
2501
+ const calculateTints = (colorValue) => {
2502
+ return calculate(colorValue, rgbTint).concat('ffffff');
2503
+ };
2504
+ const calculate = (colorValue, shadeOrTint) => {
2505
+ const color = hexToRGB(colorValue);
2506
+ const shadeValues = [];
2507
+ for (let i = 0; i < step; i++) {
2508
+ shadeValues[i] = rgbToHex(shadeOrTint(color, i));
2509
+ }
2510
+ return shadeValues;
2511
+ };
2512
+ const rgbShade = (rgb, i) => {
2513
+ return { red: rgb.red * (1 - percent * i), green: rgb.green * (1 - percent * i), blue: rgb.blue * (1 - percent * i) };
2514
+ };
2515
+ const rgbTint = (rgb, i) => {
2516
+ return { red: rgb.red + (255 - rgb.red) * i * percent, green: rgb.green + (255 - rgb.green) * i * percent, blue: rgb.blue + (255 - rgb.blue) * i * percent };
2517
+ };
2518
+ const rgbToHex = (rgb) => {
2519
+ return intToHex(rgb.red) + intToHex(rgb.green) + intToHex(rgb.blue);
2520
+ };
2521
+ const hexToRGB = (colorValue) => {
2522
+ return { red: parseInt(colorValue.substr(0, 2), 16), green: parseInt(colorValue.substr(2, 2), 16), blue: parseInt(colorValue.substr(4, 2), 16) };
2523
+ };
2524
+ const intToHex = (rgbint) => {
2525
+ return pad(Math.min(Math.max(Math.round(rgbint), 0), 255).toString(16), 2);
2526
+ };
2527
+ const pad = (number, length) => {
2528
+ let str = '' + number;
2529
+ while (str.length < length) {
2530
+ str = '0' + str;
2531
+ }
2532
+ return str;
2533
+ };
2534
+ const listColorDefine = [
2535
+ '#E62222',
2536
+ '#B81B1B',
2537
+ '#EB4E4E',
2538
+ '#F97316',
2539
+ '#C75C12',
2540
+ '#FA8F45',
2541
+ '#FFB700',
2542
+ '#CC9200',
2543
+ '#FFC533',
2544
+ '#84CC16',
2545
+ '#6AA312',
2546
+ '#9dd645',
2547
+ '#00BC62',
2548
+ '#00A757',
2549
+ '#33DA8A',
2550
+ '#06B6D4',
2551
+ '#1B59C4',
2552
+ '#4E8CF7',
2553
+ '#0EA5E9',
2554
+ '#1B59C4',
2555
+ '#4E8CF7',
2556
+ '#226FF5',
2557
+ '#1B59C4',
2558
+ '#4E8CF7',
2559
+ '#6366F1',
2560
+ '#4F52C1',
2561
+ '#8285F4',
2562
+ '#5B04B3',
2563
+ '#49038F',
2564
+ '#7C36C2',
2565
+ '#D946EF',
2566
+ '#AE38BF',
2567
+ '#E16BF2',
2568
+ '#EC4899',
2569
+ '#BD3A7A',
2570
+ '#F06DAD',
2571
+ '#F43F5E',
2572
+ '#C3324B',
2573
+ '#F6657E',
2574
+ '#757380',
2575
+ '#5E5C66',
2576
+ '#918F99',
2577
+ '#202020',
2578
+ '#1A1A1A',
2579
+ '#4D4D4D',
2580
+ ];
2581
+ const getColorById = (str) => {
2582
+ let hashString = 0;
2583
+ if (!str) {
2584
+ return '';
2585
+ }
2586
+ for (let i = 0; i < str.length; i++) {
2587
+ const char = str.charCodeAt(i);
2588
+ hashString = (hashString << 5) - hashString + char;
2589
+ hashString = hashString & hashString;
2590
+ }
2591
+ return listColorDefine[Math.abs(hashString) % listColorDefine.length];
2592
+ };
2593
+
2594
+ class UtilsKeyCodeConstant {
2595
+ static MAC_ENTER = 3;
2596
+ static BACKSPACE = 8;
2597
+ static TAB = 9;
2598
+ static NUM_CENTER = 12;
2599
+ static ENTER = 13;
2600
+ static SHIFT = 16;
2601
+ static CONTROL = 17;
2602
+ static ALT = 18;
2603
+ static PAUSE = 19;
2604
+ static CAPS_LOCK = 20;
2605
+ static ESCAPE = 27;
2606
+ static SPACE = 32;
2607
+ static PAGE_UP = 33;
2608
+ static PAGE_DOWN = 34;
2609
+ static END = 35;
2610
+ static HOME = 36;
2611
+ static LEFT_ARROW = 37;
2612
+ static UP_ARROW = 38;
2613
+ static RIGHT_ARROW = 39;
2614
+ static DOWN_ARROW = 40;
2615
+ static PLUS_SIGN = 43;
2616
+ static PRINT_SCREEN = 44;
2617
+ static INSERT = 45;
2618
+ static DELETE = 46;
2619
+ static ZERO = 48;
2620
+ static ONE = 49;
2621
+ static TWO = 50;
2622
+ static THREE = 51;
2623
+ static FOUR = 52;
2624
+ static FIVE = 53;
2625
+ static SIX = 54;
2626
+ static SEVEN = 55;
2627
+ static EIGHT = 56;
2628
+ static NINE = 57;
2629
+ static FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
2630
+ static FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
2631
+ static QUESTION_MARK = 63;
2632
+ static AT_SIGN = 64;
2633
+ static A = 65;
2634
+ static B = 66;
2635
+ static C = 67;
2636
+ static D = 68;
2637
+ static E = 69;
2638
+ static F = 70;
2639
+ static G = 71;
2640
+ static H = 72;
2641
+ static I = 73;
2642
+ static J = 74;
2643
+ static K = 75;
2644
+ static L = 76;
2645
+ static M = 77;
2646
+ static N = 78;
2647
+ static O = 79;
2648
+ static P = 80;
2649
+ static Q = 81;
2650
+ static R = 82;
2651
+ static S = 83;
2652
+ static T = 84;
2653
+ static U = 85;
2654
+ static V = 86;
2655
+ static W = 87;
2656
+ static X = 88;
2657
+ static Y = 89;
2658
+ static Z = 90;
2659
+ static META = 91; // WIN_KEY_LEFT
2660
+ static MAC_WK_CMD_LEFT = 91;
2661
+ static MAC_WK_CMD_RIGHT = 93;
2662
+ static CONTEXT_MENU = 93;
2663
+ static NUMPAD_ZERO = 96;
2664
+ static NUMPAD_ONE = 97;
2665
+ static NUMPAD_TWO = 98;
2666
+ static NUMPAD_THREE = 99;
2667
+ static NUMPAD_FOUR = 100;
2668
+ static NUMPAD_FIVE = 101;
2669
+ static NUMPAD_SIX = 102;
2670
+ static NUMPAD_SEVEN = 103;
2671
+ static NUMPAD_EIGHT = 104;
2672
+ static NUMPAD_NINE = 105;
2673
+ static NUMPAD_MULTIPLY = 106;
2674
+ static NUMPAD_PLUS = 107;
2675
+ static NUMPAD_MINUS = 109;
2676
+ static NUMPAD_PERIOD = 110;
2677
+ static NUMPAD_DIVIDE = 111;
2678
+ static F1 = 112;
2679
+ static F2 = 113;
2680
+ static F3 = 114;
2681
+ static F4 = 115;
2682
+ static F5 = 116;
2683
+ static F6 = 117;
2684
+ static F7 = 118;
2685
+ static F8 = 119;
2686
+ static F9 = 120;
2687
+ static F10 = 121;
2688
+ static F11 = 122;
2689
+ static F12 = 123;
2690
+ static NUM_LOCK = 144;
2691
+ static SCROLL_LOCK = 145;
2692
+ static FIRST_MEDIA = 166;
2693
+ static FF_MINUS = 173;
2694
+ static MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
2695
+ static VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
2696
+ static VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
2697
+ static FF_MUTE = 181;
2698
+ static FF_VOLUME_DOWN = 182;
2699
+ static LAST_MEDIA = 183;
2700
+ static FF_VOLUME_UP = 183;
2701
+ static SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
2702
+ static EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
2703
+ static COMMA = 188;
2704
+ static DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
2705
+ static PERIOD = 190;
2706
+ static SLASH = 191;
2707
+ static APOSTROPHE = 192;
2708
+ static TILDE = 192;
2709
+ static OPEN_SQUARE_BRACKET = 219;
2710
+ static BACKSLASH = 220;
2711
+ static CLOSE_SQUARE_BRACKET = 221;
2712
+ static SINGLE_QUOTE = 222;
2713
+ static MAC_META = 224;
2714
+ static BUFFERED = 229;
2715
+ }
2716
+
2717
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2718
+ const getKeyCacheByArrayObject = (keyCache, argumentsValue) => {
2719
+ if (!keyCache || !argumentsValue) {
2720
+ return '';
2721
+ }
2722
+ let keyBuild = `${JSON.stringify(argumentsValue.slice(1))}-key-cache-plus`;
2723
+ argumentsValue.forEach((item) => {
2724
+ if (typeof item === 'object') {
2725
+ if (Array.isArray(item)) {
2726
+ keyBuild = `${keyBuild}${JSON.stringify(item)}`;
2727
+ return;
2728
+ }
2729
+ const keys = (item instanceof HttpParams ? item.keys() : Object.keys(item)).sort((str1, str2) => str1.localeCompare(str2));
2730
+ keys.forEach((key) => {
2731
+ if (key.toLocaleLowerCase() === 'pem') {
2732
+ return;
2733
+ }
2734
+ keyBuild = `${keyBuild}${JSON.stringify(get(item, key))}`;
2735
+ });
2736
+ return;
2737
+ }
2738
+ keyBuild = `${keyBuild}${item}`;
2739
+ });
2740
+ const keyCachePlus = md5(keyBuild);
2741
+ const keyCacheMD5 = md5(keyCache);
2742
+ return `${keyCacheMD5}-${md5(`${keyCacheMD5}-${keyCachePlus}`)}`;
2743
+ };
2744
+
2745
+ const formatNumber = (value) => {
2746
+ const lang = UtilsCache.getLang();
2747
+ if (lang === UtilsLanguageConstants.EN) {
2748
+ return `${value}`.replace(/[,]/g, '');
2749
+ }
2750
+ return `${value}`.replace(/[.]/g, '').replace(/[,]/g, '.');
2751
+ };
2752
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2753
+ const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false) => {
2754
+ const lang = UtilsCache.getLang();
2755
+ if (!`${value}`.trim()) {
2756
+ return '';
2757
+ }
2758
+ if (isNil(value) || (!acceptNegativeValue && +value <= 0)) {
2759
+ return 0;
2760
+ }
2761
+ if (typeof value === 'string' && !ignoreFormatSeparator) {
2762
+ value = formatNumber(value);
2763
+ }
2764
+ const isFloat = value.toString().includes('.');
2765
+ const [intStr, floatStr] = value.toString().split('.');
2766
+ if (!floatStr?.length) {
2767
+ parseFixed = 0;
2768
+ }
2769
+ const round = (value, parseFixed) => {
2770
+ const fixed = Math.pow(10, parseFixed);
2771
+ return Math.round(value * fixed) / fixed;
2772
+ };
2773
+ if (parseFixed > (floatStr?.length || 0)) {
2774
+ const maxParseFixed = acceptNegativeValue && +value < 0 ? 17 : 16;
2775
+ const fixed = maxParseFixed - (intStr?.length || 0);
2776
+ parseFixed = parseFixed < fixed ? parseFixed : fixed;
2777
+ }
2778
+ if (isFloat && !ignoreParseFloat) {
2779
+ value = parseFloat(round(+value, parseFixed).toFixed(parseFixed));
2780
+ }
2781
+ // Xử lý không format dữ liệu phần thập phân
2782
+ const valueString = value.toString();
2783
+ const index = valueString.indexOf('.');
2784
+ if (index !== -1 && (parseFixed > 3 || ignoreParseFloat)) {
2785
+ let decimalPart = valueString.substring(index + 1, valueString.length);
2786
+ let int = valueString.substring(0, index + 1);
2787
+ if (ignoreParseFloat) {
2788
+ decimalPart = decimalPart.substring(0, parseFixed);
2789
+ }
2790
+ if (lang === UtilsLanguageConstants.EN) {
2791
+ int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
2792
+ return `${int}${decimalPart}`;
2793
+ }
2794
+ int = int.replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.');
2795
+ return `${int}${decimalPart}`;
2796
+ }
2797
+ if (index === -1 || parseFixed <= 3) {
2798
+ if (lang === UtilsLanguageConstants.EN) {
2799
+ return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
2800
+ }
2801
+ return value
2802
+ .toString()
2803
+ .replace('.', ',')
2804
+ .replace(/\B(?=(\d{3})+(?!\d))/g, '.');
2805
+ }
2806
+ };
2807
+
2808
+ let functionXssFilter = async (value) => {
2809
+ return value;
2810
+ };
2811
+ const updateFunctionXssFilter = (functionCustom) => {
2812
+ functionXssFilter = functionCustom;
2813
+ };
2814
+ const xssFilter = async (data) => {
2815
+ return await functionXssFilter(data);
2816
+ };
2817
+
2818
+ const ENCODE_URI_PATTERN = /%([0-9A-F]{2})/g;
2819
+ const decodeURI = (value) => {
2820
+ return decodeURIComponent(value
2821
+ .split('')
2822
+ .map((c) => {
2823
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
2824
+ })
2825
+ .join(''));
2826
+ };
2827
+ const encodeURI = (value) => {
2828
+ return encodeURIComponent(value).replace(ENCODE_URI_PATTERN, (match, p1) => {
2829
+ return String.fromCharCode(parseInt(p1, 16));
2830
+ });
2831
+ };
2832
+ const endCodeUrl = (params, isBody) => {
2833
+ params = omitBy(params, (param) => param === '' || isNil(param));
2834
+ let res = '';
2835
+ for (const p in params) {
2836
+ res += `&${p}=${encodeURIComponent(params[p])}`;
2837
+ }
2838
+ return res === '' ? '' : `${isBody ? '' : '?'}${res.substring(1)}`;
2839
+ };
2840
+
2841
+ const base64Encode = (value) => {
2842
+ return btoa(encodeURI(value));
2843
+ };
2844
+ const base64Decode = (value) => {
2845
+ return decodeURI(atob(value));
2846
+ };
2847
+ const convertBase64ToBlob = (dataBase64) => {
2848
+ const BASE64_MARKER = ';base64,';
2849
+ if (dataBase64.indexOf(BASE64_MARKER) === -1) {
2850
+ const parts = dataBase64.split(',');
2851
+ const contentType = parts[0].split(':')[1];
2852
+ const raw = parts[1];
2853
+ const blob = new Blob([raw], { type: contentType });
2854
+ return blob;
2855
+ }
2856
+ const parts = dataBase64.split(BASE64_MARKER);
2857
+ const contentType = parts[0].split(':')[1];
2858
+ const raw = window.atob(parts[1]);
2859
+ const rawLength = raw.length;
2860
+ const uInt8Array = new Uint8Array(rawLength);
2861
+ for (let i = 0; i < rawLength; ++i) {
2862
+ uInt8Array[i] = raw.charCodeAt(i);
2863
+ }
2864
+ const blob = new Blob([uInt8Array], { type: contentType });
2865
+ return blob;
2866
+ };
2867
+ const convertFileToBase64_ObjectUrl = async (file) => {
2868
+ if (!file.type.match(/image.*/)) {
2869
+ return URL.createObjectURL(file);
2870
+ }
2871
+ return await convertFileToBase64(file);
2872
+ };
2873
+ const convertFileToBase64 = (file) => {
2874
+ return new Promise((resolve) => {
2875
+ const reader = new FileReader();
2876
+ reader.readAsDataURL(file);
2877
+ reader.onload = (event) => {
2878
+ resolve(event.target?.result ?? '');
2879
+ };
2880
+ });
2881
+ };
2882
+
2883
+ const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
2884
+ const xmlRequest = new XMLHttpRequest();
2885
+ xmlRequest.open('GET', fileUrl, true);
2886
+ xmlRequest.responseType = 'blob';
2887
+ xmlRequest.onerror = () => {
2888
+ const url = window.URL.createObjectURL(xmlRequest.response);
2889
+ downloadFileByUrl(url, filename);
2890
+ };
2891
+ xmlRequest.onload = () => {
2892
+ const url = window.URL.createObjectURL(xmlRequest.response);
2893
+ downloadFileByUrl(url, filename);
2894
+ };
2895
+ xmlRequest.send();
2896
+ };
2897
+ const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
2898
+ const downloadFileElement = document.createElement('a');
2899
+ if (!onlyOpen) {
2900
+ const response = await fetch(fileUrl);
2901
+ if (response.ok) {
2902
+ const blob = await response.blob();
2903
+ fileUrl = URL.createObjectURL(blob);
2904
+ }
2905
+ }
2906
+ downloadFileElement.href = fileUrl;
2907
+ downloadFileElement.download = filename;
2908
+ downloadFileElement.target = '_blank';
2909
+ downloadFileElement.click();
2910
+ downloadFileElement.remove();
2911
+ };
2912
+ const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
2913
+ const parentElement = imageElement?.src;
2914
+ const blobData = convertBase64ToBlob(parentElement);
2915
+ const blob = new Blob([blobData], { type: typeFileDownload || 'image/png' });
2916
+ const url = window.URL.createObjectURL(blob);
2917
+ const link = document.createElement('a');
2918
+ link.href = url;
2919
+ link.download = nameFile || 'barcode';
2920
+ link.click();
2921
+ };
2922
+
2923
+ const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN_INJECT');
2924
+ const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
2925
+ const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');
2926
+
2927
+ const isTypeImage = (file) => (file.type.match(/image.*/) ? true : false);
2928
+ const isTypeVideo = (file) => (file.type.match(/video.*/) ? true : false);
2929
+ const isTypeAudio = (file) => (file.type.match(/audio.*/) ? true : false);
2930
+ const isTypeFile = (file) => (file instanceof File || Object.prototype.toString.call(file) === '[object File]' ? true : false);
2931
+ const ExcelExtList = ['xls', 'xlsx', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
2932
+ const DocumentExtList = [
2933
+ 'doc',
2934
+ 'docx',
2935
+ 'xls',
2936
+ 'xlsx',
2937
+ 'ppt',
2938
+ 'pptx',
2939
+ 'pdf',
2940
+ 'json',
2941
+ 'xml',
2942
+ 'application/msword',
2943
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2944
+ 'application/vnd.ms-excel',
2945
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2946
+ 'application/vnd.ms-powerpoint',
2947
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2948
+ 'application/pdf',
2949
+ 'application/json',
2950
+ 'application/xml',
2951
+ ];
2952
+ const isIncludeDocumentExtList = (ext, listExt = DocumentExtList) => listExt.includes(ext) || listExt.includes(`application/${ext}`);
2953
+ const ImageExtList = ['gif', 'jpg', 'jpeg', 'png', 'image/gif', 'image/jpeg', 'image/jpeg', 'image/png'];
2954
+ const isIncludeImageExtList = (ext, listExt = ImageExtList) => listExt.includes(ext);
2955
+ const VideoExtList = ['mp4', 'mov', 'mpg', 'avi', 'wmv', 'video/mp4', 'video/quicktime', 'video/mpeg', 'video/x-msvideo', 'video/x-ms-wmv'];
2956
+ const isIncludeVideoExtList = (ext, listExt = VideoExtList) => listExt.includes(ext);
2957
+ const AudioExtList = ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'wma', 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/aac', 'audio/mp4', 'audio/flac', 'audio/x-ms-wma'];
2958
+ const isIncludeAudioExtList = (ext, listExt = AudioExtList) => listExt.includes(ext);
2959
+ const getFileExtension = (file) => {
2960
+ if (file instanceof File && file.type) {
2961
+ return (file.type.split('/').pop() || '').toLowerCase();
2962
+ }
2963
+ file = file;
2964
+ if (file.mimetype || file.file?.type) {
2965
+ return file.mimetype || file.file?.type;
2966
+ }
2967
+ let fileName = file.name;
2968
+ const url = get(file, 'url') || get(file, 'origin_url');
2969
+ if (!fileName && !url) {
2970
+ return;
2971
+ }
2972
+ if (!fileName && url) {
2973
+ fileName = url.split('/').pop() || '';
2974
+ set(file, 'name', fileName);
2975
+ }
2976
+ const dots = fileName.split('.');
2977
+ if (!dots) {
2978
+ return;
2979
+ }
2980
+ return (dots.pop() || '').toLowerCase();
2981
+ };
2982
+ const getLabelBySizeFile = (size, toFixed = 2) => {
2983
+ if (size < 1024 * 1024) {
2984
+ return ` ${(size / 1024).toFixed(toFixed)} KB`;
2985
+ }
2986
+ return ` ${(size / (1024 * 1024)).toFixed(toFixed)} MB`;
2987
+ };
2988
+ const convertBlobToFile = (blob, fileName) => {
2989
+ const type = blob.type.split('/')[1];
2990
+ const name = fileName ? `${fileName.split('.')[0]}.${type}` : `${uuid()}.${type}`;
2991
+ return new File([blob], name, { type: blob.type, lastModified: Date.now() });
2992
+ };
2993
+ const convertUrlToFile = (url, fileName) => {
2994
+ return new Promise((resolve, reject) => {
2995
+ const xhr = new XMLHttpRequest();
2996
+ xhr.onload = function () {
2997
+ const reader = new FileReader();
2998
+ reader.onloadend = () => {
2999
+ const file = convertBase64ToBlob(reader.result);
3000
+ resolve(convertBlobToFile(file, fileName || Date.now().toLocaleString()));
3001
+ };
3002
+ reader.onerror = () => resolve(undefined);
3003
+ reader.readAsDataURL(xhr.response);
3004
+ };
3005
+ xhr.onerror = (err) => {
3006
+ reject(err);
3007
+ };
3008
+ xhr.open('GET', url);
3009
+ xhr.responseType = 'blob';
3010
+ xhr.send();
3011
+ });
3012
+ };
3013
+
3014
+ const DEFAULT_MIN_TICK_COUNT = 5;
3015
+ const DEFAULT_MAX_TICK_COUNT = 10;
3016
+ const MIN_DISTANCE_TO_ZERO = 3;
3017
+ const NEGATIVE_THRESHOLD = -5;
3018
+ const FALLBACK_NEGATIVE_VALUE = -4;
3019
+ const MAX_POW_NUMBER = 14;
3020
+ const MAX_TEMPLATE_NUMBER = 10;
3021
+ /**
3022
+ * Tính toán smart axis scale cho biểu đồ
3023
+ *
3024
+ * @param maxData - Giá trị tối đa của dữ liệu
3025
+ * @param options - Các tùy chọn cấu hình axis scale
3026
+ * @returns Cấu hình axis scale bao gồm stepSize, max, min, tickAmount
3027
+ *
3028
+ * @throws {Error} INVALID_NEGATIVE_DATA - khi maxData < 0 mà acceptNegative = false
3029
+ * @throws {Error} MISSING_MIN_NEGATIVE - khi acceptNegative = true nhưng thiếu minNegative
3030
+ * @throws {Error} INVALID_RANGE - khi maxData < minNegative
3031
+ * @throws {Error} INVALID_MIN_NEGATIVE - khi minNegative >= 0
3032
+ *
3033
+ * @example
3034
+ * ```typescript
3035
+ * const result = getSmartAxisScale(100, { minTickCount: 5, maxTickCount: 10 });
3036
+ * // returns { stepSize: 20, max: 120, min: 0, tickAmount: 6 }
3037
+ * ```
3038
+ */
3039
+ const getSmartAxisScale = (originalMaxData, options) => {
3040
+ let maxData = originalMaxData;
3041
+ validateInputs(maxData, options);
3042
+ const minTickCount = options?.minTickCount || DEFAULT_MIN_TICK_COUNT;
3043
+ const maxTickCount = options?.maxTickCount || DEFAULT_MAX_TICK_COUNT;
3044
+ let scaleDirection = 1;
3045
+ let rangeDistance = maxData;
3046
+ if (options?.acceptNegative && !isNil(options.minNegative)) {
3047
+ if (maxData === 0) {
3048
+ maxData = -1;
3049
+ }
3050
+ if (maxData <= 0) {
3051
+ rangeDistance = options.minNegative;
3052
+ }
3053
+ if (rangeDistance > NEGATIVE_THRESHOLD) {
3054
+ rangeDistance = FALLBACK_NEGATIVE_VALUE;
3055
+ }
3056
+ if (maxData > 0) {
3057
+ rangeDistance = maxData + Math.abs(options.minNegative);
3058
+ scaleDirection = -1;
3059
+ }
3060
+ }
3061
+ if (Math.abs(rangeDistance) < MIN_DISTANCE_TO_ZERO) {
3062
+ rangeDistance = MIN_DISTANCE_TO_ZERO;
3063
+ }
3064
+ const stepCandidates = getStepCandidates(maxData, minTickCount, options?.minNegative || 0, options?.acceptStepIsTypeFloat, options?.stepCandidates, options?.acceptNegative);
3065
+ for (const step of stepCandidates) {
3066
+ let tickCount = Math.abs(Math.ceil(rangeDistance / step)) + (scaleDirection === -1 ? 2 : 1);
3067
+ let maxValue = maxData <= 0 ? 0 : step * tickCount * scaleDirection;
3068
+ let minValue = 0;
3069
+ if (tickCount >= minTickCount && tickCount <= maxTickCount) {
3070
+ if (maxData < maxValue) {
3071
+ if (options?.acceptNegative) {
3072
+ let tick = 1;
3073
+ while (!isNil(options.minNegative) && tick <= tickCount && minValue >= options.minNegative) {
3074
+ minValue = tick * step;
3075
+ tick++;
3076
+ }
3077
+ }
3078
+ const maxValuePositive = maxValue - Math.abs(minValue);
3079
+ tickCount = maxValuePositive - originalMaxData > Math.abs(step) && tickCount - 1 >= minTickCount ? tickCount - 1 : tickCount;
3080
+ maxValue = step * tickCount * scaleDirection - minValue * scaleDirection;
3081
+ return {
3082
+ stepSize: Math.abs(step),
3083
+ max: maxValue,
3084
+ min: minValue,
3085
+ tickAmount: tickCount,
3086
+ };
3087
+ }
3088
+ }
3089
+ }
3090
+ let step = Math.ceil(rangeDistance / minTickCount) || 1;
3091
+ let maxValue = step * minTickCount;
3092
+ if (rangeDistance === maxValue) {
3093
+ step = step + Math.ceil(step / 10);
3094
+ maxValue = step * minTickCount;
3095
+ }
3096
+ return {
3097
+ stepSize: Math.abs(step),
3098
+ max: maxValue,
3099
+ min: 0,
3100
+ tickAmount: minTickCount,
3101
+ };
3102
+ };
3103
+ // Cache cho các giá trị lũy thừa 10 để tối ưu hiệu suất
3104
+ const POWER_CACHE = new Map();
3105
+ /**
3106
+ * Tạo danh sách các step candidates cho việc tính toán scale
3107
+ * @param maxData - Giá trị dữ liệu tối đa
3108
+ * @param minStep - Số bước tối thiểu
3109
+ * @param minNegative - Giá trị âm tối thiểu
3110
+ * @param acceptStepIsTypeFloat - Có chấp nhận step thập phân không
3111
+ * @param stepCandidatesByOptions - Step candidates do người dùng cung cấp
3112
+ * @param includeNegativeSteps - Có bao gồm các step âm không
3113
+ * @returns Danh sách các step candidates
3114
+ */
3115
+ const getStepCandidates = (maxData, minStep, minNegative, acceptStepIsTypeFloat = false, stepCandidatesByOptions, includeNegativeSteps = false) => {
3116
+ // Nếu có step candidates tùy chỉnh, sử dụng chúng
3117
+ if (stepCandidatesByOptions && stepCandidatesByOptions.length > 0) {
3118
+ return stepCandidatesByOptions;
3119
+ }
3120
+ const stepCandidates = new Array();
3121
+ const maxValueStep = Math.abs(Math.max(maxData, Math.abs(minNegative || 0))) / minStep;
3122
+ // Tạo step candidates dựa trên lũy thừa của 10
3123
+ for (let powNumber = 0; powNumber <= MAX_POW_NUMBER; powNumber++) {
3124
+ // Sử dụng cache để tối ưu hiệu suất
3125
+ if (!POWER_CACHE.has(powNumber)) {
3126
+ POWER_CACHE.set(powNumber, Math.pow(10, powNumber));
3127
+ }
3128
+ const powValue = POWER_CACHE.get(powNumber);
3129
+ for (let templateNumber = 1; templateNumber < MAX_TEMPLATE_NUMBER; templateNumber++) {
3130
+ // Thêm step thập phân nếu được chấp nhận
3131
+ if (acceptStepIsTypeFloat) {
3132
+ const step = powValue * (((templateNumber - 1) * 2 + 1) / 2);
3133
+ stepCandidates.push(step);
3134
+ }
3135
+ const step = powValue * templateNumber;
3136
+ stepCandidates.push(step);
3137
+ // Dừng khi step đã đủ lớn
3138
+ if (step >= maxValueStep) {
3139
+ checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
3140
+ return stepCandidates;
3141
+ }
3142
+ }
3143
+ }
3144
+ checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
3145
+ return stepCandidates;
3146
+ };
3147
+ /**
3148
+ * Kiểm tra và thêm các step âm vào danh sách candidates nếu cần
3149
+ * @param stepCandidates - Danh sách step candidates hiện tại
3150
+ * @param acceptNegative - Có chấp nhận giá trị âm không
3151
+ * @param minNegative - Giá trị âm tối thiểu
3152
+ */
3153
+ const checkAndSetNegativeSteps = (stepCandidates, acceptNegative, minNegative) => {
3154
+ if (acceptNegative && minNegative < 0) {
3155
+ // Tạo các step âm và thêm vào đầu danh sách
3156
+ const negativeSteps = [...stepCandidates].map((step) => -step);
3157
+ stepCandidates.unshift(...negativeSteps);
3158
+ }
3159
+ };
3160
+ /**
3161
+ * Kiểm tra tính hợp lệ của các tham số đầu vào
3162
+ * @param maxData - Giá trị dữ liệu tối đa
3163
+ * @param options - Các tùy chọn cấu hình
3164
+ * @throws {Error} Khi các tham số không hợp lệ
3165
+ */
3166
+ const validateInputs = (maxData, options) => {
3167
+ // Kiểm tra maxData âm khi không chấp nhận giá trị âm
3168
+ if (maxData < 0 && !options?.acceptNegative) {
3169
+ throw new Error('maxData is less than 0 and acceptNegative is false');
3170
+ }
3171
+ if (options?.acceptNegative) {
3172
+ // Kiểm tra minNegative có được cung cấp không
3173
+ if (isNil(options.minNegative)) {
3174
+ throw new Error('minNegative is required when acceptNegative is true');
3175
+ }
3176
+ // Kiểm tra maxData phải >= minNegative
3177
+ if (maxData < options.minNegative) {
3178
+ throw new Error('maxData is less than minNegative');
3179
+ }
3180
+ // Kiểm tra minNegative phải là số âm
3181
+ if (options.minNegative >= 0) {
3182
+ throw new Error('minNegative must be negative');
3183
+ }
3184
+ }
3185
+ };
3186
+
3187
+ const protectString = (input) => {
3188
+ const xorKey = 77;
3189
+ const encoded = input
3190
+ .split('')
3191
+ .map((char) => String.fromCharCode(char.charCodeAt(0) ^ xorKey))
3192
+ .reverse()
3193
+ .join('');
3194
+ return btoa(encoded);
3195
+ };
3196
+ const revealString = (encoded) => {
3197
+ const xorKey = 77;
3198
+ const base = atob(encoded);
3199
+ const decoded = base
3200
+ .split('')
3201
+ .reverse()
3202
+ .map((c) => String.fromCharCode(c.charCodeAt(0) ^ xorKey))
3203
+ .join('');
3204
+ return decoded;
3205
+ };
3206
+ const createUniqueRandomIntGenerator = (min, max) => {
3207
+ if (max - min + 1 < min + 10) {
3208
+ throw new Error(`Range too small: need at least min + 10 unique values`);
3209
+ }
3210
+ const history = [];
3211
+ return () => {
3212
+ let num;
3213
+ let loop = 0;
3214
+ do {
3215
+ num = Math.floor(Math.random() * (max - min + 1)) + min;
3216
+ loop++;
3217
+ if (loop > 1000) {
3218
+ break;
3219
+ }
3220
+ } while (history.includes(num));
3221
+ history.push(num);
3222
+ if (history.length > 10) {
3223
+ history.shift();
3224
+ }
3225
+ return num;
3226
+ };
3227
+ };
3228
+
3229
+ /**
3230
+ * Generated bundle index. Do not edit.
3231
+ */
3232
+
3233
+ export { AudioExtList, CHARACTER_DATA_EMPTY, COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, COMMUNICATE_MICRO_PREFIX_TYPE, DEFAULT_START_PAGE_0, DocumentExtList, ENCODE_URI_PATTERN, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MAX_LENGTH, ERROR_MESSAGE_MAX_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MIN_VALID, ERROR_MESSAGE_PATTEN_VALID, ExcelExtList, ImageExtList, LINK_IMAGE_ERROR_TOKEN_INJECT, PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT, PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT, UtilsCache, UtilsCommunicateMicro, UtilsCommunicateMicroKeyGlobal, UtilsHttpParamsRequest, UtilsHttpParamsRequestInstance, UtilsKeyCodeConstant, UtilsLanguageConstants, UtilsUrlSearchParams, VideoExtList, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertHtmlToDivBlocks, convertObjectToSignal, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeltaFromHTML, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getHTMLFromQuill, getKeyCacheByArrayObject, getLabelBySizeFile, getPlatFromBrowser, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, insertContentWithRange, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNil, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, omitBy, patternAccount, patternDomain, patternEmail, patternEmoji, patternEncodeUri, patternGetFieldByRuleFieldReplace, patternHostUrl, patternKey, patternMobilePhone, patternName, patternNameProfile, patternNameSpecial, patternNameUtf8, patternNumber, patternPem, patternPhone, patternPhoneNormal, patternRuleFieldReplace, patternTax, patternUrl, processPasteData, protectString, range, removeEmoji, revealString, rgbToHex, set, setCaretPosition, setDefaultTimeZone, setKeyCrypto, setKeyCrypto3rd, setStylesElement, uniqBy, unwrapSignal, updateFunctionCheckEmbedFrame, updateFunctionFormatDate, updateFunctionXssFilter, uppercaseByPosition, uuid, viewDataNumberByLanguage, xssFilter };
3234
+ //# sourceMappingURL=libs-ui-utils.mjs.map