@libs-ui/utils 0.2.355-9 → 0.2.356-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +633 -2
  2. package/base64.d.ts +25 -0
  3. package/cache.d.ts +73 -0
  4. package/collection.d.ts +12 -0
  5. package/color.d.ts +37 -0
  6. package/communicate-micro.d.ts +34 -1
  7. package/crypto-3rd.d.ts +12 -0
  8. package/crypto.d.ts +17 -0
  9. package/dangerous-object.d.ts +8 -1
  10. package/data.d.ts +5 -3
  11. package/date.d.ts +12 -0
  12. package/dom.d.ts +56 -0
  13. package/download.d.ts +21 -0
  14. package/esm2022/base64.mjs +27 -2
  15. package/esm2022/cache.mjs +74 -1
  16. package/esm2022/collection.mjs +13 -1
  17. package/esm2022/color.mjs +38 -1
  18. package/esm2022/communicate-micro.mjs +37 -5
  19. package/esm2022/crypto-3rd.mjs +13 -1
  20. package/esm2022/crypto.mjs +18 -1
  21. package/esm2022/dangerous-object.mjs +11 -2
  22. package/esm2022/data.mjs +6 -4
  23. package/esm2022/date.mjs +13 -1
  24. package/esm2022/dom.mjs +57 -1
  25. package/esm2022/download.mjs +22 -1
  26. package/esm2022/file.mjs +54 -1
  27. package/esm2022/format-number.mjs +31 -3
  28. package/esm2022/format-text.mjs +73 -2
  29. package/esm2022/function-check-embed-frame.mjs +28 -1
  30. package/esm2022/pattern.mjs +83 -3
  31. package/esm2022/trace.mjs +8 -1
  32. package/esm2022/two-way-signal-object.mjs +14 -1
  33. package/esm2022/uri.mjs +22 -3
  34. package/esm2022/url-search-params.mjs +68 -1
  35. package/esm2022/url.mjs +10 -1
  36. package/esm2022/uuid.mjs +7 -1
  37. package/esm2022/xss-filter.mjs +14 -1
  38. package/fesm2022/libs-ui-utils.mjs +723 -21
  39. package/fesm2022/libs-ui-utils.mjs.map +1 -1
  40. package/file.d.ts +53 -0
  41. package/format-number.d.ts +29 -1
  42. package/format-text.d.ts +71 -0
  43. package/function-check-embed-frame.d.ts +22 -0
  44. package/package.json +2 -2
  45. package/pattern.d.ts +81 -1
  46. package/trace.d.ts +7 -0
  47. package/two-way-signal-object.d.ts +13 -0
  48. package/uri.d.ts +20 -0
  49. package/url-search-params.d.ts +67 -0
  50. package/url.d.ts +9 -0
  51. package/uuid.d.ts +6 -0
  52. package/xss-filter.d.ts +13 -0
@@ -157,6 +157,26 @@ const isReturnAsIsObject = (obj) => {
157
157
  const isSafeToProcess = (obj) => {
158
158
  return !isDangerousObject(obj);
159
159
  };
160
+ /**
161
+ * Kiểm tra xem giá trị có phải là kiểu dữ liệu nguyên thủy (Primitive) không.
162
+ * Bao gồm: string, number, boolean, symbol, bigint, null, undefined.
163
+ * @param value Giá trị cần kiểm tra
164
+ * @returns true nếu là kiểu nguyên thủy
165
+ */
166
+ const isPrimitiveType = (value) => {
167
+ return value === null || value === undefined || ['string', 'number', 'boolean', 'symbol', 'bigint'].includes(typeof value);
168
+ };
169
+
170
+ const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
171
+ const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
172
+ const ERROR_MESSAGE_MIN_VALID = 'i18n_message_error_input_min_value';
173
+ const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
174
+ const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
175
+ const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
176
+ const CHARACTER_DATA_EMPTY = '__';
177
+ const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
178
+ const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
179
+ const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
160
180
 
161
181
  let key$1 = '12~@#loqwsxacva(3rdhaq12';
162
182
  /**
@@ -172,6 +192,12 @@ const setKeyCrypto = (value) => {
172
192
  const keyStore$1 = () => {
173
193
  return key$1;
174
194
  };
195
+ /**
196
+ * Mã hóa dữ liệu dạng chuỗi bằng thuật toán AES-CBC.
197
+ * Yêu cầu key mã hóa đã được thiết lập qua setKeyCrypto.
198
+ * @param plainData Chuỗi văn bản thuần túy cần mã hóa
199
+ * @returns Chuỗi kết quả đã được mã hóa (Base64 string)
200
+ */
175
201
  const encrypt = (plainData) => {
176
202
  if (!keyStore$1()) {
177
203
  throw Error('lỗi chưa setup key mã hóa');
@@ -183,6 +209,12 @@ const encrypt = (plainData) => {
183
209
  const options = { iv: iv, mode: mode, padding: padding };
184
210
  return CryptoES.AES.encrypt(plainData, key, options).toString();
185
211
  };
212
+ /**
213
+ * Giải mã dữ liệu đã được mã hóa bằng AES-CBC.
214
+ * Yêu cầu cùng một key mã hóa đã dùng để encrypt.
215
+ * @param encryptedData Chuỗi dữ liệu đã mã hóa
216
+ * @returns Chuỗi văn bản thuần túy sau khi giải mã (UTF-8)
217
+ */
186
218
  const decrypt = (encryptedData) => {
187
219
  if (!keyStore$1()) {
188
220
  throw Error('lỗi chưa setup key mã hóa');
@@ -194,6 +226,11 @@ const decrypt = (encryptedData) => {
194
226
  const options = { iv: iv, mode: mode, padding: padding };
195
227
  return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
196
228
  };
229
+ /**
230
+ * Tạo mã hash MD5 từ chuỗi văn bản.
231
+ * @param plainData Chuỗi văn bản cần hash
232
+ * @returns Chuỗi mã hash MD5 (32 ký tự hex)
233
+ */
197
234
  const md5 = (plainData) => {
198
235
  return CryptoES.MD5(plainData).toString();
199
236
  };
@@ -212,6 +249,12 @@ const setKeyCrypto3rd = (value) => {
212
249
  const keyStore = () => {
213
250
  return key;
214
251
  };
252
+ /**
253
+ * Mã hóa dữ liệu dạng chuỗi bằng thuật toán AES-CBC.
254
+ * Yêu cầu key mã hóa đã được thiết lập qua setKeyCrypto3rd.
255
+ * @param plainData Chuỗi văn bản thuần túy cần mã hóa
256
+ * @returns Chuỗi kết quả đã được mã hóa (Base64 string)
257
+ */
215
258
  const encrypt3rd = (plainData) => {
216
259
  if (!keyStore()) {
217
260
  throw Error('lỗi chưa setup key mã hóa');
@@ -223,6 +266,12 @@ const encrypt3rd = (plainData) => {
223
266
  const options = { iv: iv, mode: mode, padding: padding };
224
267
  return CryptoES.AES.encrypt(plainData, key, options).toString();
225
268
  };
269
+ /**
270
+ * Giải mã dữ liệu đã được mã hóa bằng AES-CBC.
271
+ * Yêu cầu cùng một key mã hóa đã dùng để encrypt.
272
+ * @param encryptedData Chuỗi dữ liệu đã mã hóa
273
+ * @returns Chuỗi văn bản thuần túy sau khi giải mã (UTF-8)
274
+ */
226
275
  const decrypt3rd = (encryptedData) => {
227
276
  if (!keyStore()) {
228
277
  throw Error('lỗi chưa setup key mã hóa');
@@ -236,36 +285,66 @@ const decrypt3rd = (encryptedData) => {
236
285
  };
237
286
 
238
287
  //NOTE: Do là function truyền từ bên ngoài vào nên sẽ không có unit test
288
+ /**
289
+ * Hàm kiểm tra mặc định — phát hiện ứng dụng đang chạy trong iframe/embed frame
290
+ * bằng cách so sánh `window.parent` với `window.top`.
291
+ * Có thể bị ghi đè bởi `updateFunctionCheckEmbedFrame` cho môi trường tùy chỉnh.
292
+ */
239
293
  let functionCheck = () => {
240
294
  return window.parent !== window.top;
241
295
  };
296
+ /**
297
+ * Ghi đè hàm kiểm tra embed frame bằng một hàm logic tùy chỉnh.
298
+ * Hữu ích trong môi trường Micro-frontend hoặc khi cần mock cho testing.
299
+ *
300
+ * @param functionCustom Hàm trả về `true` nếu app đang chạy trong iframe/embed frame
301
+ * @example
302
+ * // Micro-frontend: luôn coi là embed
303
+ * updateFunctionCheckEmbedFrame(() => true);
304
+ *
305
+ * // Testing: luôn coi là không embed
306
+ * updateFunctionCheckEmbedFrame(() => false);
307
+ */
242
308
  const updateFunctionCheckEmbedFrame = (functionCustom) => {
243
309
  functionCheck = functionCustom;
244
310
  };
311
+ /**
312
+ * Kiểm tra ứng dụng hiện tại có đang được nhúng trong iframe/embed frame hay không.
313
+ * Sử dụng hàm logic đang được đăng ký (mặc định hoặc custom).
314
+ *
315
+ * @returns `true` nếu app đang chạy trong iframe/embed, `false` nếu đang chạy standalone
316
+ * @example
317
+ * if (isEmbedFrame()) {
318
+ * // Ẩn header, sidebar vì đã có container bên ngoài quản lý
319
+ * }
320
+ */
245
321
  const isEmbedFrame = () => {
246
322
  return functionCheck();
247
323
  };
248
324
 
249
- const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
250
- const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
251
- const ERROR_MESSAGE_MIN_VALID = 'i18n_message_error_input_min_value';
252
- const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
253
- const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
254
- const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
255
- const CHARACTER_DATA_EMPTY = '__';
256
- const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
257
- const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
258
- const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
259
-
260
- /* eslint-disable @typescript-eslint/no-explicit-any */
325
+ /**
326
+ * Cấu hình các key global cho truyền thông micro
327
+ */
261
328
  class UtilsCommunicateMicroKeyGlobal {
329
+ /** Key sự kiện cho modal */
262
330
  static KEY_MESSAGE_MODAL = 'LIBS_UI_MODEL_EVENT';
331
+ /** Bỏ qua cập nhật thời gian live event modal */
263
332
  static IGNORE_INTERVAL_UPDATE_TIME_LIVE_EVENT_MODAL = false;
264
333
  }
334
+ /**
335
+ * Tiện ích hỗ trợ truyền thông giữa các cửa sổ, iframes (Cross-document communication)
336
+ * Sử dụng Window.postMessage kèm theo mã hóa/giải mã dữ liệu tự động.
337
+ */
265
338
  class UtilsCommunicateMicro {
266
339
  static initdEvent;
267
340
  static subs = new Map();
268
341
  static allMessageSub = new Subject();
342
+ /**
343
+ * Khởi tạo trình lắng nghe sự kiện 'message' trên window hiện tại.
344
+ * Cần được gọi một lần duy nhất tại Root Component (AppComponent).
345
+ * @param currentWindow Đối tượng window hiện tại
346
+ * @param destroyRef Reference để tự động unsubscribe khi component bị hủy
347
+ */
269
348
  static initEvent(currentWindow, destroyRef) {
270
349
  if (this.initdEvent) {
271
350
  return;
@@ -311,6 +390,11 @@ class UtilsCommunicateMicro {
311
390
  UtilsCache.ClearAll();
312
391
  });
313
392
  }
393
+ /**
394
+ * Gửi message tới cửa sổ cha (Parent) hoặc top window.
395
+ * Thường dùng khi app chạy trong Iframe.
396
+ * @param data Dữ liệu cần gửi ({type: string, response: any})
397
+ */
314
398
  static PostMessageToParent(data) {
315
399
  data = this.convertData(data);
316
400
  try {
@@ -324,6 +408,10 @@ class UtilsCommunicateMicro {
324
408
  console.log(error);
325
409
  }
326
410
  }
411
+ /**
412
+ * Gửi message tới tất cả các iframe con có trong trang.
413
+ * @param data Dữ liệu cần gửi ({type: string, response: any})
414
+ */
327
415
  static PostMessageToChildren(data) {
328
416
  data = this.convertData(data);
329
417
  const iframes = document.querySelectorAll('iframe');
@@ -331,6 +419,10 @@ class UtilsCommunicateMicro {
331
419
  iframe?.contentWindow?.postMessage(data, '*');
332
420
  });
333
421
  }
422
+ /**
423
+ * Gửi message tới cửa sổ đã mở cửa sổ hiện tại (Window Opener).
424
+ * @param data Dữ liệu cần gửi ({type: string, response: any})
425
+ */
334
426
  static PostMessageToOpener(data) {
335
427
  if (!window.opener) {
336
428
  return;
@@ -363,6 +455,11 @@ class UtilsCommunicateMicro {
363
455
  return data;
364
456
  }
365
457
  }
458
+ /**
459
+ * Đăng ký lắng nghe các message theo type cụ thể. Trả về một RxJS Subject.
460
+ * @param messageType Type của message hoặc danh sách các types cần lắng nghe
461
+ * @returns Subject sẽ phát ra message event khi nhận được dữ liệu hợp lệ
462
+ */
366
463
  static GetMessage(messageType) {
367
464
  if (!this.initdEvent) {
368
465
  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');
@@ -483,6 +580,12 @@ class UtilsLanguageConstants {
483
580
  }
484
581
  }
485
582
 
583
+ /**
584
+ * Tạo một chuỗi định danh duy nhất (Unique ID) dựa trên thời gian (timestamp) và các ký tự ngẫu nhiên.
585
+ * Chuỗi kết quả được xáo trộn và băm bằng MD5, trả về một chuỗi 32 ký tự alphanumeric.
586
+ *
587
+ * @returns {string} Chuỗi định danh duy nhất (MD5 hash).
588
+ */
486
589
  const uuid = () => {
487
590
  const timestamp = performance.now() * 1000;
488
591
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
@@ -502,6 +605,10 @@ const uuid = () => {
502
605
 
503
606
  /* eslint-disable no-async-promise-executor */
504
607
  /* eslint-disable @typescript-eslint/no-explicit-any */
608
+ /**
609
+ * Tiện ích quản lý Cache, hỗ trợ cả LocalStorage (đồng bộ) và IndexedDB (bất đồng bộ)
610
+ * Tự động mã hóa dữ liệu và xử lý thời gian hết hạn
611
+ */
505
612
  class UtilsCache {
506
613
  static CACHE_EXPIRE_TIME_DEFAULT = 5 * 60;
507
614
  static CACHE_EXPIRE_NONE = -1;
@@ -515,6 +622,10 @@ class UtilsCache {
515
622
  static itemIndexByKey = 'key';
516
623
  static dbVersion = 1;
517
624
  static db = null;
625
+ /**
626
+ * Khởi tạo cấu hình cho Cache
627
+ * @param config Cấu hình khởi tạo (tên DB, key ngôn ngữ, event clear...)
628
+ */
518
629
  static init(config) {
519
630
  if (this.initdEvent) {
520
631
  return;
@@ -533,12 +644,20 @@ class UtilsCache {
533
644
  this.languageKeyCache = config.languageKeyCache;
534
645
  }
535
646
  }
647
+ /**
648
+ * Lưu ngôn ngữ hiện tại vào cache
649
+ * @param lang Mã ngôn ngữ (vi, en, ...)
650
+ */
536
651
  static setLang(lang) {
537
652
  if (!UtilsLanguageConstants.isSupported(lang)) {
538
653
  throw Error(`Language not support ${lang}`);
539
654
  }
540
655
  this.Set(this.languageKeyCache, lang, this.CACHE_EXPIRE_NONE);
541
656
  }
657
+ /**
658
+ * Lấy ngôn ngữ từ cache
659
+ * @returns Mã ngôn ngữ
660
+ */
542
661
  static getLang() {
543
662
  return this.Get(this.languageKeyCache, UtilsLanguageConstants.defaultLang());
544
663
  }
@@ -636,6 +755,13 @@ class UtilsCache {
636
755
  },
637
756
  };
638
757
  }
758
+ /**
759
+ * Lấy giá trị từ IndexedDB theo key (bất đồng bộ)
760
+ * @param key Khóa cache
761
+ * @param default_value Giá trị mặc định nếu không tìm thấy
762
+ * @param isKeyMD5 Nếu true, key đã là MD5. Nếu false (mặc định), sẽ tự động MD5 key.
763
+ * @returns Promise chứa giá trị đã giải mã
764
+ */
639
765
  static async GetAsync(key, default_value, isKeyMD5 = false) {
640
766
  key = isKeyMD5 ? key : md5(key);
641
767
  return new Promise(async (resolve) => {
@@ -664,6 +790,12 @@ class UtilsCache {
664
790
  };
665
791
  });
666
792
  }
793
+ /**
794
+ * Lấy giá trị từ LocalStorage theo key (đồng bộ)
795
+ * @param key Khóa cache
796
+ * @param default_value Giá trị mặc định nếu không tìm thấy
797
+ * @returns Giá trị đã giải mã hoặc giá trị mặc định
798
+ */
667
799
  static Get(key, default_value) {
668
800
  // support cho những file không thể inject UtilsCache
669
801
  if (!key) {
@@ -692,6 +824,14 @@ class UtilsCache {
692
824
  static GetDefaultValueBySpecificKey(key, default_value) {
693
825
  return default_value;
694
826
  }
827
+ /**
828
+ * Lưu giá trị vào IndexedDB (bất đồng bộ)
829
+ * @param key Khóa cache
830
+ * @param value Giá trị cần lưu
831
+ * @param expireTimeBySecond Thời gian hết hạn (giây). Mặc định 5 phút. Dùng CACHE_EXPIRE_NONE để vĩnh viễn.
832
+ * @param isKeyMD5 Nếu true, key đã là MD5.
833
+ * @returns Promise chứa thông tin kết quả
834
+ */
695
835
  static async SetAsync(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT, isKeyMD5 = false) {
696
836
  // support inject UtilsCache
697
837
  return new Promise(async (resolve) => {
@@ -723,6 +863,13 @@ class UtilsCache {
723
863
  }
724
864
  });
725
865
  }
866
+ /**
867
+ * Lưu giá trị vào LocalStorage (đồng bộ)
868
+ * @param key Khóa cache
869
+ * @param value Giá trị cần lưu
870
+ * @param expireTimeBySecond Thời gian hết hạn (giây). Mặc định 5 phút.
871
+ * @returns true nếu thành công
872
+ */
726
873
  static Set(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT) {
727
874
  // support cho những file không inject UtilsCache
728
875
  const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
@@ -739,6 +886,11 @@ class UtilsCache {
739
886
  return false;
740
887
  }
741
888
  }
889
+ /**
890
+ * Xóa một key trong IndexedDB
891
+ * @param key Khóa cần xóa
892
+ * @param isMD5 Nếu true, key đã được MD5.
893
+ */
742
894
  static async ClearAsync(key, isMD5 = false) {
743
895
  return new Promise(async (resolve) => {
744
896
  const objectStore = await this.getObjectStore();
@@ -755,12 +907,19 @@ class UtilsCache {
755
907
  };
756
908
  });
757
909
  }
910
+ /**
911
+ * Xóa một key trong LocalStorage
912
+ * @param key Khóa cần xóa
913
+ */
758
914
  static Clear(key) {
759
915
  if (key.includes('kc-callback-')) {
760
916
  return;
761
917
  }
762
918
  this.LocalStorage.removeItem(key);
763
919
  }
920
+ /**
921
+ * Xóa toàn bộ dữ liệu trong IndexedDB
922
+ */
764
923
  static ClearAllAsync() {
765
924
  return new Promise(async (resolve) => {
766
925
  const objectStore = await this.getObjectStore();
@@ -778,6 +937,9 @@ class UtilsCache {
778
937
  };
779
938
  });
780
939
  }
940
+ /**
941
+ * Xóa toàn bộ dữ liệu trong LocalStorage (kiểm soát các key đặc biệt)
942
+ */
781
943
  static ClearAll() {
782
944
  if (isEmbedFrame()) {
783
945
  const data = {
@@ -821,6 +983,11 @@ class UtilsCache {
821
983
  this.Set(key, value, this.CACHE_EXPIRE_NONE);
822
984
  });
823
985
  }
986
+ /**
987
+ * Xóa tất cả các khóa bắt đầu bằng tiền tố nhất định trong IndexedDB
988
+ * @param keyCacheStartWith Tiền tố khóa
989
+ * @param isKeyMD5 Nếu true, tiền tố đã được MD5.
990
+ */
824
991
  static DeleteKeyStartWithAsync(keyCacheStartWith, isKeyMD5 = false) {
825
992
  return new Promise(async (resolve) => {
826
993
  const objectStore = await this.getObjectStore();
@@ -847,6 +1014,11 @@ class UtilsCache {
847
1014
  };
848
1015
  });
849
1016
  }
1017
+ /**
1018
+ * Xóa tất cả các khóa bắt đầu bằng tiền tố nhất định trong LocalStorage
1019
+ * @param keyCache Tiền tố khóa
1020
+ * @param isMD5 Nếu true, tiền tố sẽ được MD5 trước khi so sánh.
1021
+ */
850
1022
  static DeleteKeyStartWith(keyCache, isMD5 = false) {
851
1023
  keyCache = isMD5 ? md5(keyCache) : keyCache;
852
1024
  const keys = Object.keys(this.LocalStorage);
@@ -859,6 +1031,10 @@ class UtilsCache {
859
1031
  }
860
1032
  });
861
1033
  }
1034
+ /**
1035
+ * Xóa hoàn toàn cơ sở dữ liệu IndexedDB
1036
+ * @param dbName Tên DB, mặc định theo cấu hình.
1037
+ */
862
1038
  static DeleteDatabaseIndexDB(dbName) {
863
1039
  return new Promise((resolve) => {
864
1040
  dbName = (dbName || this.dbName);
@@ -887,14 +1063,26 @@ dayjs.extend(timezone);
887
1063
  dayjs.extend(customParseFormat);
888
1064
  let timeZoneSetup = 'Asia/Ho_Chi_Minh';
889
1065
  let patternCheckTimeUTC = undefined;
1066
+ /**
1067
+ * Thiết lập múi giờ mặc định cho hệ thống (mặc định là Asia/Ho_Chi_Minh).
1068
+ * @param localeZone Tên múi giờ (ví dụ: 'America/New_York')
1069
+ */
890
1070
  const setDefaultTimeZone = (localeZone = timeZoneSetup) => {
891
1071
  timeZoneSetup = localeZone;
892
1072
  dayjs.tz.setDefault(localeZone);
893
1073
  };
1074
+ /**
1075
+ * Thiết lập pattern Regex để nhận diện và chuẩn hóa các chuỗi thời gian UTC đặc biệt.
1076
+ * @param pattern Regex pattern dùng để kiểm tra và replace chuỗi thời gian
1077
+ */
894
1078
  const setPatternCheckTimeUTC = (pattern) => {
895
1079
  patternCheckTimeUTC = pattern;
896
1080
  };
897
1081
  let functionFormatDate = undefined;
1082
+ /**
1083
+ * Cung cấp một hàm format tùy chỉnh ghi đè lên logic mặc định của formatDate.
1084
+ * @param functionCustom Hàm format tùy chỉnh
1085
+ */
898
1086
  const updateFunctionFormatDate = (functionCustom) => {
899
1087
  functionFormatDate = functionCustom;
900
1088
  };
@@ -1212,6 +1400,14 @@ const convertObjectToSignal = (data, cloneDeepIfNotSignal = true, isSignalPrimit
1212
1400
  }
1213
1401
  return data;
1214
1402
  };
1403
+ /**
1404
+ * Chuyển đổi một đối tượng signal (có thể lồng nhau) thành đối tượng plain object/array/map/set.
1405
+ * @param data Dữ liệu signal hoặc đối tượng chứa signal cần chuyển đổi.
1406
+ * @param isCloneDeep Có thực hiện sao chép sâu dữ liệu trong quá trình chuyển đổi hay không. Mặc định là true.
1407
+ * @param seen Dùng nội bộ để tránh lặp vô hạn (WeakMap).
1408
+ * @param options Tùy chọn chuyển đổi (ví dụ: convertWhenFirstItemArrayNotIsSignal).
1409
+ * @returns Đối tượng plain object đã được "giải nén" mọi signal bên trong.
1410
+ */
1215
1411
  const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap(), options) => {
1216
1412
  let ignoreCheckSeenHasDataAfterWhile = false;
1217
1413
  while (isSignal(data) && !seen.has(data)) {
@@ -1276,6 +1472,11 @@ const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap(), o
1276
1472
  }
1277
1473
  return result;
1278
1474
  };
1475
+ /**
1476
+ * Unwrap một giá trị cho đến khi không còn là signal nữa.
1477
+ * @param data Giá trị (có thể là lồng nhiều cấp signal) cần unwrap.
1478
+ * @returns Giá trị cuối cùng bên trong signal.
1479
+ */
1279
1480
  const unwrapSignal = (data) => {
1280
1481
  while (isSignal(data)) {
1281
1482
  data = data();
@@ -1930,13 +2131,24 @@ const generateInterface = (obj, interfaceName) => {
1930
2131
 
1931
2132
  let parser = null;
1932
2133
  let bowserParser = null;
2134
+ /**
2135
+ * Lấy thông tin thiết bị và trình duyệt (Sử dụng thư viện Bowser).
2136
+ * @returns Đối tượng Parser chứa thông tin OS, Browser, Platform...
2137
+ */
1933
2138
  const getDeviceInfo = () => {
1934
2139
  if (!bowserParser) {
1935
2140
  bowserParser = Bowser.getParser(window.navigator.userAgent);
1936
2141
  }
1937
2142
  return bowserParser;
1938
2143
  };
2144
+ /**
2145
+ * Kiểm tra xem thiết bị hiện tại có hỗ trợ cảm ứng (Touch) không.
2146
+ * @returns true nếu là thiết bị cảm ứng
2147
+ */
1939
2148
  const isTouchDevice = () => ('onpointerdown' in navigator && navigator.maxTouchPoints > 0) || 'ontouchstart' in window || (window.matchMedia?.('(pointer: coarse)').matches ?? false);
2149
+ /**
2150
+ * Xác định tên sự kiện click phù hợp cho thiết bị (click cho desktop, pointerdown cho touch).
2151
+ */
1940
2152
  const getEventNameHandleClick = (() => {
1941
2153
  if (isTouchDevice()) {
1942
2154
  return 'pointerdown';
@@ -1944,12 +2156,22 @@ const getEventNameHandleClick = (() => {
1944
2156
  const deviceInfo = getDeviceInfo();
1945
2157
  return deviceInfo.isPlatform('desktop') ? 'click' : 'touchstart';
1946
2158
  })();
2159
+ /**
2160
+ * Chuyển đổi một chuỗi HTML thành đối tượng Document.
2161
+ * @param htmlStr Chuỗi HTML cần parse
2162
+ * @returns Đối tượng Document tương ứng
2163
+ */
1947
2164
  const getDocumentByString = (htmlStr) => {
1948
2165
  if (!parser) {
1949
2166
  parser = new DOMParser();
1950
2167
  }
1951
2168
  return parser.parseFromString(htmlStr, 'text/html');
1952
2169
  };
2170
+ /**
2171
+ * Sao chép các thuộc tính cơ bản của IBoundingClientRect.
2172
+ * @param rect Đối tượng IBoundingClientRect gốc
2173
+ * @returns Đối tượng IBoundingClientRect đã được copy
2174
+ */
1953
2175
  const cloneIBoundingClientRect = (rect) => {
1954
2176
  return {
1955
2177
  top: rect['top'],
@@ -1959,13 +2181,28 @@ const cloneIBoundingClientRect = (rect) => {
1959
2181
  bottom: rect['bottom'],
1960
2182
  };
1961
2183
  };
2184
+ /**
2185
+ * Gán nhiều thuộc tính CSS style cho một Element.
2186
+ * @param el Phần tử HTML cần gán style
2187
+ * @param styles Đối tượng chứa các style (ví dụ: { color: 'red', display: 'block' })
2188
+ */
1962
2189
  const setStylesElement = (el, styles) => {
1963
2190
  Object.keys(styles).forEach((key) => set(el, `style.${key}`, styles[key]));
1964
2191
  };
2192
+ /**
2193
+ * Lấy kích thước Viewport hiển thị của cửa sổ trình duyệt.
2194
+ * @param win Đối tượng Window (mặc định là window)
2195
+ * @returns Đối tượng chứa width và height
2196
+ */
1965
2197
  const getViewport = (win = window) => {
1966
2198
  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;
1967
2199
  return { width: w, height: h };
1968
2200
  };
2201
+ /**
2202
+ * Thiết lập vị trí con trỏ (caret) cho thẻ Input hoặc Textarea.
2203
+ * @param element Thẻ input hoặc textarea
2204
+ * @param position Vị trí cần đặt con trỏ
2205
+ */
1969
2206
  const setCaretPosition = (element, position) => {
1970
2207
  if (!element || !element.setSelectionRange) {
1971
2208
  return;
@@ -1973,6 +2210,14 @@ const setCaretPosition = (element, position) => {
1973
2210
  element.focus();
1974
2211
  element.setSelectionRange(position, position);
1975
2212
  };
2213
+ /**
2214
+ * Kiểm tra xem một phần tử có đang hiển thị trong vùng nhìn thấy (viewport) của một container không.
2215
+ * @param container Element cha chứa vùng scroll
2216
+ * @param element Element cần kiểm tra
2217
+ * @param elementScroll (Tùy chọn) Element đại diện cho vùng scroll nếu khác container
2218
+ * @param maxTopLeft (Tùy chọn) Offset bổ sung để kiểm tra
2219
+ * @returns true nếu nằm trong vùng nhìn thấy
2220
+ */
1976
2221
  const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
1977
2222
  if (!container || !element) {
1978
2223
  return false;
@@ -2000,6 +2245,13 @@ const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
2000
2245
  }
2001
2246
  return false;
2002
2247
  };
2248
+ /**
2249
+ * Kiểm tra xem tọa độ chuột hiện tại có nằm trong vùng của một Element không.
2250
+ * @param mousePosition Đối tượng chứa clientX, clientY
2251
+ * @param element Element cần kiểm tra
2252
+ * @param rect (Tùy chọn) Bounding rect đã có sẵn để tối ưu hiệu năng
2253
+ * @returns true nếu chuột đang đè lên phần tử
2254
+ */
2003
2255
  const checkMouseOverInContainer = (mousePosition, element, rect) => {
2004
2256
  if (!element && !rect) {
2005
2257
  return false;
@@ -2014,6 +2266,11 @@ const checkMouseOverInContainer = (mousePosition, element, rect) => {
2014
2266
  }
2015
2267
  return false;
2016
2268
  };
2269
+ /**
2270
+ * Tạo sự kiện Kéo-Thả (Drag) nâng cao dựa trên RxJS cho một Element.
2271
+ * @param config Cấu hình sự kiện (element, các hàm callback, destroyRef)
2272
+ * @returns Observable phát ra sự kiện mousemove trong quá trình kéo
2273
+ */
2017
2274
  const getDragEventByElement = (config) => {
2018
2275
  let timer;
2019
2276
  const addClass = () => {
@@ -2050,20 +2307,37 @@ const getDragEventByElement = (config) => {
2050
2307
  return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntilDestroyed(config.destroyRef), finalize(removeClass));
2051
2308
  };
2052
2309
 
2310
+ /**
2311
+ * Tiện ích xử lý tham số truy vấn trên URL (URL Search Parameters).
2312
+ * Cung cấp các phương thức để parse, thêm, xóa, lấy giá trị và so sánh các tham số.
2313
+ */
2053
2314
  class UtilsUrlSearchParams {
2054
2315
  static instance;
2055
2316
  params;
2317
+ /**
2318
+ * Khởi tạo UtilsUrlSearchParams từ một chuỗi query string.
2319
+ * @param paramString Chuỗi tham số (ví dụ: "?id=1&name=test" hoặc đơn giản là "id=1&name=test").
2320
+ */
2056
2321
  constructor(paramString) {
2057
2322
  this.params = new Array();
2058
2323
  this.params.length = 0;
2059
2324
  this.params.push(...this.buildParams(paramString));
2060
2325
  }
2326
+ /**
2327
+ * Lấy instance duy nhất (Singleton) của UtilsUrlSearchParams.
2328
+ * @returns {UtilsUrlSearchParams} Instance của lớp.
2329
+ */
2061
2330
  static getInstance() {
2062
2331
  if (!this.instance) {
2063
2332
  this.instance = new UtilsUrlSearchParams('');
2064
2333
  }
2065
2334
  return this.instance;
2066
2335
  }
2336
+ /**
2337
+ * Chuyển đổi một đối tượng key-value thành chuỗi query string.
2338
+ * @param params Đối tượng chứa các tham số (ví dụ: { id: "1", name: "test" }).
2339
+ * @returns {string} Chuỗi query string đã được sắp xếp theo key (ví dụ: "id=1&name=test").
2340
+ */
2067
2341
  static toStringParamObject(params) {
2068
2342
  const paramsArr = Object.keys(params).map((key) => {
2069
2343
  return {
@@ -2073,6 +2347,12 @@ class UtilsUrlSearchParams {
2073
2347
  });
2074
2348
  return UtilsUrlSearchParams.ToString(paramsArr);
2075
2349
  }
2350
+ /**
2351
+ * Chuyển đổi mảng các cặp key-value thành chuỗi query string.
2352
+ * Các tham số sẽ được sắp xếp theo thứ tự bảng chữ cái của key.
2353
+ * @param params Mảng các đối tượng { key, value }.
2354
+ * @returns {string} Chuỗi query string.
2355
+ */
2076
2356
  static ToString(params) {
2077
2357
  let paramsStr = '';
2078
2358
  params
@@ -2083,6 +2363,12 @@ class UtilsUrlSearchParams {
2083
2363
  });
2084
2364
  return paramsStr;
2085
2365
  }
2366
+ /**
2367
+ * Phân tích chuỗi query string thành mảng các cặp key-value.
2368
+ * Tự động decode các giá trị và xử lý các trường hợp URL lỗi (nhiều dấu ?).
2369
+ * @param paramString Chuỗi cần phân tích.
2370
+ * @returns {Array<{ key: string; value: string }>} Mảng kết quả.
2371
+ */
2086
2372
  buildParams(paramString) {
2087
2373
  const paramsBuild = new Array();
2088
2374
  if (!paramString) {
@@ -2108,6 +2394,11 @@ class UtilsUrlSearchParams {
2108
2394
  }
2109
2395
  return paramsBuild;
2110
2396
  }
2397
+ /**
2398
+ * Thêm hoặc cập nhật giá trị của một tham số.
2399
+ * @param key Tên tham số.
2400
+ * @param value Giá trị tham số.
2401
+ */
2111
2402
  set(key, value) {
2112
2403
  const paramByKey = this.params.find((param) => param.key === key);
2113
2404
  if (!paramByKey) {
@@ -2116,25 +2407,51 @@ class UtilsUrlSearchParams {
2116
2407
  }
2117
2408
  paramByKey['value'] = value;
2118
2409
  }
2410
+ /**
2411
+ * Lấy giá trị của một tham số theo key.
2412
+ * @param key Tên tham số cần lấy.
2413
+ * @returns {string | undefined} Giá trị của tham số hoặc undefined nếu không tìm thấy.
2414
+ */
2119
2415
  get(key) {
2120
2416
  return this.params.find((param) => param.key === key)?.value;
2121
2417
  }
2418
+ /**
2419
+ * Kiểm tra xem một tham số có tồn tại hay không.
2420
+ * @param key Tên tham số cần kiểm tra.
2421
+ * @returns {boolean} True nếu tồn tại.
2422
+ */
2122
2423
  has(key) {
2123
2424
  return this.params.some((param) => param.key === key);
2124
2425
  }
2426
+ /**
2427
+ * Xóa một tham số khỏi danh sách.
2428
+ * @param key Tên tham số cần xóa.
2429
+ */
2125
2430
  delete(key) {
2126
2431
  const index = this.params.findIndex((param) => param.key === key);
2127
2432
  if (index >= 0) {
2128
2433
  this.params.splice(index, 1);
2129
2434
  }
2130
2435
  }
2436
+ /**
2437
+ * @returns {number} Số lượng tham số hiện có.
2438
+ */
2131
2439
  length() {
2132
2440
  return this.params.length;
2133
2441
  }
2442
+ /**
2443
+ * Chuyển đổi các tham số hiện tại (hoặc mảng truyền vào) thành chuỗi query string.
2444
+ * @param params (Tùy chọn) Mảng tham số cần convert. Nếu không truyền sẽ dùng params nội bộ.
2445
+ * @returns {string} Chuỗi query string.
2446
+ */
2134
2447
  toString(params) {
2135
2448
  params = params || this.params;
2136
2449
  return UtilsUrlSearchParams.ToString(params);
2137
2450
  }
2451
+ /**
2452
+ * Chuyển đổi danh sách tham số hiện tại thành một đối tượng plain object.
2453
+ * @returns {Record<string, string>} Đối tượng key-value.
2454
+ */
2138
2455
  getParamObject() {
2139
2456
  const params = {};
2140
2457
  this.params.forEach((item) => {
@@ -2142,6 +2459,13 @@ class UtilsUrlSearchParams {
2142
2459
  });
2143
2460
  return params;
2144
2461
  }
2462
+ /**
2463
+ * So sánh hai chuỗi query string xem chúng có chứa cùng tập tham số và giá trị hay không.
2464
+ * Thứ tự các tham số trong chuỗi ban đầu không quan trọng.
2465
+ * @param param1 Chuỗi thứ nhất.
2466
+ * @param param2 Chuỗi thứ hai.
2467
+ * @returns {boolean} True nếu giống nhau.
2468
+ */
2145
2469
  compareParams(param1, param2) {
2146
2470
  const params1 = this.toString(this.buildParams(param1));
2147
2471
  const params2 = this.toString(this.buildParams(param2));
@@ -2151,9 +2475,20 @@ class UtilsUrlSearchParams {
2151
2475
 
2152
2476
  const step = 20;
2153
2477
  const percent = 0.05;
2478
+ /**
2479
+ * Lấy cụ thể một shade hoặc tint của màu dựa trên bước (step)
2480
+ * @param color Mã màu HEX (3 hoặc 6 ký tự)
2481
+ * @param stepNumber Bước màu cần lấy (5, 10, 15... 100)
2482
+ * @returns Đối tượng chứa thông tin bước màu và mã HEX light/dark
2483
+ */
2154
2484
  const colorStepContrastFromOrigin = (color, stepNumber) => {
2155
2485
  return colorContrastFromOrigin(color).find((item) => item.step === stepNumber);
2156
2486
  };
2487
+ /**
2488
+ * Tạo ra danh sách các sắc độ (shades & tints) từ một màu gốc
2489
+ * @param color Mã màu HEX
2490
+ * @returns Mảng các đối tượng chứa step, label, light hex và dark hex
2491
+ */
2157
2492
  const colorContrastFromOrigin = (color) => {
2158
2493
  const parsedColorsArray = parseColorValues(color);
2159
2494
  const colors = [];
@@ -2206,6 +2541,11 @@ const rgbShade = (rgb, i) => {
2206
2541
  const rgbTint = (rgb, i) => {
2207
2542
  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 };
2208
2543
  };
2544
+ /**
2545
+ * Chuyển đổi đối tượng RGB sang mã HEX (không có dấu #)
2546
+ * @param rgb Đối tượng chứa red, green, blue
2547
+ * @returns Chuỗi HEX 6 ký tự
2548
+ */
2209
2549
  const rgbToHex = (rgb) => {
2210
2550
  return intToHex(rgb.red) + intToHex(rgb.green) + intToHex(rgb.blue);
2211
2551
  };
@@ -2222,6 +2562,9 @@ const pad = (number, length) => {
2222
2562
  }
2223
2563
  return str;
2224
2564
  };
2565
+ /**
2566
+ * Danh sách các mã màu định nghĩa sẵn để sử dụng trong getColorById
2567
+ */
2225
2568
  const listColorDefine = [
2226
2569
  '#E62222',
2227
2570
  '#B81B1B',
@@ -2269,6 +2612,12 @@ const listColorDefine = [
2269
2612
  '#1A1A1A',
2270
2613
  '#4D4D4D',
2271
2614
  ];
2615
+ /**
2616
+ * Lấy một màu định nghĩa sẵn dựa trên chuỗi đầu vào (như ID người dùng, tên...)
2617
+ * Màu trả về là duy nhất và cố định cho cùng một chuỗi đầu vào.
2618
+ * @param str Chuỗi để hash lấy màu
2619
+ * @returns Mã màu HEX từ listColorDefine
2620
+ */
2272
2621
  const getColorById = (str) => {
2273
2622
  let hashString = 0;
2274
2623
  if (!str) {
@@ -2281,6 +2630,12 @@ const getColorById = (str) => {
2281
2630
  }
2282
2631
  return listColorDefine[Math.abs(hashString) % listColorDefine.length];
2283
2632
  };
2633
+ /**
2634
+ * Loại bỏ các thuộc tính màu sắc (color, background...) "gần trắng" khỏi chuỗi style CSS
2635
+ * Giúp tránh việc hiển thị chữ trắng trên nền trắng khi merge style lung tung.
2636
+ * @param style Chuỗi style CSS (ví dụ: "color: #fff; font-size: 14px;")
2637
+ * @returns Chuỗi style đã được làm sạch
2638
+ */
2284
2639
  const detectAndCleanNearWhiteColors = (style) => {
2285
2640
  if (!style)
2286
2641
  return style;
@@ -2307,6 +2662,12 @@ const detectAndCleanNearWhiteColors = (style) => {
2307
2662
  .map(([k, v]) => `${k}: ${v}`)
2308
2663
  .join('; ');
2309
2664
  };
2665
+ /**
2666
+ * Kiểm tra một màu có phải là "gần trắng" hay không
2667
+ * Dựa trên luminance > 0.95 và saturation < 0.1
2668
+ * @param color Mã màu (HEX, RGB, RGBA hoặc tên màu CSS)
2669
+ * @returns true nếu màu gần trắng
2670
+ */
2310
2671
  const isNearWhite = (color) => {
2311
2672
  if (!color)
2312
2673
  return false;
@@ -2505,6 +2866,17 @@ const getKeyCacheByArrayObject = (keyCache, argumentsValue) => {
2505
2866
  return `${keyCacheMD5}-${md5(`${keyCacheMD5}-${keyCachePlus}`)}`;
2506
2867
  };
2507
2868
 
2869
+ /**
2870
+ * Chuẩn hóa chuỗi số theo locale hiện tại về dạng số thực (parseable).
2871
+ * - Với **EN**: Xóa dấu phẩy (`,`) — dấu phân tách hàng nghìn.
2872
+ * - Với **VI**: Xóa dấu chấm (`.`) và đổi dấu phẩy (`,`) thành dấu chấm (`.`).
2873
+ *
2874
+ * @param value Chuỗi hoặc số cần chuẩn hóa
2875
+ * @returns Chuỗi số đã được chuẩn hóa (không dấu phân cách locale)
2876
+ * @example
2877
+ * // EN: "1,234,567" → "1234567"
2878
+ * // VI: "1.234.567,89" → "1234567.89"
2879
+ */
2508
2880
  const formatNumber = (value) => {
2509
2881
  const lang = UtilsCache.getLang();
2510
2882
  if (lang === UtilsLanguageConstants.EN) {
@@ -2512,9 +2884,26 @@ const formatNumber = (value) => {
2512
2884
  }
2513
2885
  return `${value}`.replace(/[.]/g, '').replace(/[,]/g, '.');
2514
2886
  };
2887
+ /**
2888
+ * Định dạng và hiển thị số theo ngôn ngữ (VI/EN) với các tùy chọn linh hoạt.
2889
+ * Xử lý số âm, phần thập phân, làm tròn và dấu phân cách hàng nghìn.
2890
+ *
2891
+ * @param value Giá trị số hoặc chuỗi số đầu vào
2892
+ * @param acceptNegativeValue Có chấp nhận số âm không (false → trả về 0 nếu value <= 0)
2893
+ * @param parseFixed Số chữ số thập phân muốn giữ lại (mặc định: 1)
2894
+ * @param ignoreFormatSeparator Bỏ qua bước chuẩn hóa dấu phân cách (khi value đã là số thuần)
2895
+ * @param ignoreParseFloat Bỏ qua bước làm tròn (giữ nguyên phần thập phân)
2896
+ * @param lang Ngôn ngữ hiển thị ('vi' hoặc 'en'), mặc định lấy từ cache
2897
+ * @returns Chuỗi số đã format theo locale (vd: "1.234.567,89" hoặc "1,234,567.89")
2898
+ * @example
2899
+ * // VI, 2 chữ số thập phân
2900
+ * viewDataNumberByLanguage(1234567.891, true, 2) → "1.234.567,89"
2901
+ * // EN, 2 chữ số thập phân
2902
+ * viewDataNumberByLanguage(1234567.891, true, 2, false, false, 'en') → "1,234,567.89"
2903
+ */
2515
2904
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2516
- const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false) => {
2517
- const lang = UtilsCache.getLang();
2905
+ const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false, lang) => {
2906
+ lang = lang || UtilsCache.getLang();
2518
2907
  if (!`${value}`.trim()) {
2519
2908
  return '';
2520
2909
  }
@@ -2569,63 +2958,143 @@ const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ig
2569
2958
  };
2570
2959
 
2571
2960
  /* eslint-disable no-useless-escape */
2961
+ /**
2962
+ * Returns regex for email validation
2963
+ * @returns {RegExp}
2964
+ */
2572
2965
  const patternEmail = () => {
2573
2966
  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]$/;
2574
2967
  };
2575
- const patterProtocolUrl = () => {
2968
+ /**
2969
+ * Returns regex for protocol in URL (http, https, ftp)
2970
+ * @returns {RegExp}
2971
+ */
2972
+ const patternProtocolUrl = () => {
2576
2973
  return /(http|https|ftp):/;
2577
2974
  };
2975
+ /**
2976
+ * Returns regex for full URL validation
2977
+ * @returns {RegExp}
2978
+ */
2578
2979
  const patternUrl = () => {
2579
2980
  return /^(http|https|ftp):(\/){2}[^\s]+[.]{1}[^\s]+$/;
2580
2981
  };
2982
+ /**
2983
+ * Returns regex for host part of URL
2984
+ * @returns {RegExp}
2985
+ */
2581
2986
  const patternHostUrl = () => {
2582
2987
  return /^((https|http|ftp):[/]{2}[^/\s]+)/;
2583
2988
  };
2989
+ /**
2990
+ * Returns regex for domain validation
2991
+ * @returns {RegExp}
2992
+ */
2584
2993
  const patternDomain = () => {
2585
- 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})+$/;
2994
+ 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,})+$/;
2586
2995
  };
2996
+ /**
2997
+ * Returns regex for Vietnamese mobile phone validation
2998
+ * @returns {RegExp}
2999
+ */
2587
3000
  const patternMobilePhone = () => {
2588
3001
  return /^(\+?84|0|84)([0-9]{9})$/;
2589
3002
  };
3003
+ /**
3004
+ * Returns regex for general phone number validation
3005
+ * @returns {RegExp}
3006
+ */
2590
3007
  const patternPhone = () => {
2591
3008
  return /^(\+?84|[0-9]|84)([0-9]{2,9})$/;
2592
3009
  };
3010
+ /**
3011
+ * Returns regex for digits
3012
+ * @returns {RegExp}
3013
+ */
2593
3014
  const patternNumber = () => {
2594
3015
  return /\d+/g;
2595
3016
  };
3017
+ /**
3018
+ * Returns regex for encoded URI characters
3019
+ * @returns {RegExp}
3020
+ */
2596
3021
  const patternEncodeUri = () => {
2597
3022
  return /%([0-9A-F]{2})/g;
2598
3023
  };
3024
+ /**
3025
+ * Returns regex for standard name (alphanumeric and spaces)
3026
+ * @returns {RegExp}
3027
+ */
2599
3028
  const patternName = () => {
2600
3029
  return /^\w+[A-Za-z\s\d]+$/;
2601
3030
  };
3031
+ /**
3032
+ * Returns regex for UTF-8 name (including Vietnamese characters)
3033
+ * @returns {RegExp}
3034
+ */
2602
3035
  const patternNameUtf8 = () => {
2603
3036
  return /^[ àáảãạăắằẵặẳâầấậẫẩđèéẻẽẹêềếểễệìíỉĩịòóỏõọôồốổỗộơờớởỡợùúủũụưừứửữựỳýỷỹÀÁẢÃẠĂẮẰẴẶẲÂẦẤẬẪẨĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸA-Za-z0-9]+$/;
2604
3037
  };
3038
+ /**
3039
+ * Returns regex for special characters in name
3040
+ * @returns {RegExp}
3041
+ */
2605
3042
  const patternNameSpecial = () => {
2606
3043
  return /[~!@#$%^&*()-+=<>,?\/\\:;"']/;
2607
3044
  };
3045
+ /**
3046
+ * Returns regex for profile name
3047
+ * @returns {RegExp}
3048
+ */
2608
3049
  const patternNameProfile = () => {
2609
3050
  return /([\w\W\d\s]+)+/;
2610
3051
  };
3052
+ /**
3053
+ * Returns regex for emojis
3054
+ * @returns {RegExp}
3055
+ */
2611
3056
  const patternEmoji = () => {
2612
3057
  return /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
2613
3058
  };
3059
+ /**
3060
+ * Returns regex for matching field replacement markers (e.g. {{field}})
3061
+ * @returns {RegExp}
3062
+ */
2614
3063
  const patternRuleFieldReplace = () => {
2615
3064
  return /[{]{2}[a-zA-Z_-]+[}]{2}/g;
2616
3065
  };
3066
+ /**
3067
+ * Returns regex for extracting field name from replacement markers
3068
+ * @returns {RegExp}
3069
+ */
2617
3070
  const patternGetFieldByRuleFieldReplace = () => {
2618
3071
  return /[a-zA-Z_-]+/g;
2619
3072
  };
3073
+ /**
3074
+ * Returns regex for PEM format validation
3075
+ * @returns {RegExp}
3076
+ */
2620
3077
  const patternPem = () => {
2621
3078
  return /^(0|1):([0-9]{1,2}):(\{\{path-api\}\}):([a-zA-Z0-9\/]+)$/g;
2622
3079
  };
3080
+ /**
3081
+ * Returns regex for tax ID validation
3082
+ * @returns {RegExp}
3083
+ */
2623
3084
  const patternTax = () => {
2624
3085
  return /^([0-9]){10}(-[0-9]{3})?$/;
2625
3086
  };
3087
+ /**
3088
+ * Returns regex for key validation (identical to tax regex)
3089
+ * @returns {RegExp}
3090
+ */
2626
3091
  const patternKey = () => {
2627
3092
  return /^([0-9]){10}(-[0-9]{3})?$/;
2628
3093
  };
3094
+ /**
3095
+ * Returns regex for account validation (must contain @)
3096
+ * @returns {RegExp}
3097
+ */
2629
3098
  const patternAccount = () => {
2630
3099
  return /^(?=.*@)[a-z0-9@._-]{2,63}$/;
2631
3100
  };
@@ -2633,14 +3102,33 @@ const patternAccount = () => {
2633
3102
  let functionXssFilter = async (value) => {
2634
3103
  return value;
2635
3104
  };
3105
+ /**
3106
+ * Cập nhật hàm lọc XSS.
3107
+ * Cho phép ghi đè hàm xử lý XSS mặc định (trả về nguyên gốc) bằng một implementation bảo mật do project cung cấp (ví dụ: dùng DOMPurify).
3108
+ *
3109
+ * @param functionCustom Hàm xử lý XSS tùy chỉnh cần cập nhật.
3110
+ */
2636
3111
  const updateFunctionXssFilter = (functionCustom) => {
2637
3112
  functionXssFilter = functionCustom;
2638
3113
  };
3114
+ /**
3115
+ * Lọc dữ liệu thông qua hàm lọc XSS đã được định cấu hình.
3116
+ * Theo mặc định, hàm này sẽ trả lại chuỗi ban đầu. Nếu bạn đã cung cấp một custom filter qua `updateFunctionXssFilter`, hàm đó sẽ được sử dụng.
3117
+ *
3118
+ * @param data Chuỗi dữ liệu đầu vào.
3119
+ * @returns {Promise<string>} Promise chứa kết quả đã được lọc XSS.
3120
+ */
2639
3121
  const xssFilter = async (data) => {
2640
3122
  return await functionXssFilter(data);
2641
3123
  };
2642
3124
 
2643
3125
  const ENCODE_URI_PATTERN = /%([0-9A-F]{2})/g;
3126
+ /**
3127
+ * Giải mã chuỗi URI một cách an toàn.
3128
+ *
3129
+ * @param value Chuỗi cần giải mã.
3130
+ * @returns Chuỗi đã được giải mã.
3131
+ */
2644
3132
  const decodeURI = (value) => {
2645
3133
  return decodeURIComponent(value
2646
3134
  .split('')
@@ -2649,11 +3137,25 @@ const decodeURI = (value) => {
2649
3137
  })
2650
3138
  .join(''));
2651
3139
  };
3140
+ /**
3141
+ * Mã hóa chuỗi URI.
3142
+ *
3143
+ * @param value Chuỗi cần mã hóa.
3144
+ * @returns Chuỗi đã được mã hóa.
3145
+ */
2652
3146
  const encodeURI = (value) => {
2653
3147
  return encodeURIComponent(value).replace(ENCODE_URI_PATTERN, (match, p1) => {
2654
3148
  return String.fromCharCode(parseInt(p1, 16));
2655
3149
  });
2656
3150
  };
3151
+ /**
3152
+ * Chuyển đổi đối tượng parameters thành chuỗi query string hoặc body string.
3153
+ * Tự động loại bỏ các giá trị rỗng, null, hoặc undefined.
3154
+ *
3155
+ * @param params Đối tượng chứa các tham số.
3156
+ * @param isBody Nếu true, trả về chuỗi không có dấu '?' ở đầu.
3157
+ * @returns Chuỗi tham số đã được mã hóa.
3158
+ */
2657
3159
  const endCodeUrl = (params, isBody) => {
2658
3160
  params = omitBy(params, (param) => param === '' || isNil(param));
2659
3161
  let res = '';
@@ -2663,12 +3165,27 @@ const endCodeUrl = (params, isBody) => {
2663
3165
  return res === '' ? '' : `${isBody ? '' : '?'}${res.substring(1)}`;
2664
3166
  };
2665
3167
 
3168
+ /**
3169
+ * Mã hóa chuỗi thành Base64 (có hỗ trợ ký tự đặc biệt qua encodeURI)
3170
+ * @param value Chuỗi cần mã hóa
3171
+ * @returns Chuỗi đã mã hóa Base64
3172
+ */
2666
3173
  const base64Encode = (value) => {
2667
3174
  return btoa(encodeURI(value));
2668
3175
  };
3176
+ /**
3177
+ * Giải mã chuỗi Base64 (có hỗ trợ ký tự đặc biệt qua decodeURI)
3178
+ * @param value Chuỗi Base64 cần giải mã
3179
+ * @returns Chuỗi đã giải mã
3180
+ */
2669
3181
  const base64Decode = (value) => {
2670
3182
  return decodeURI(atob(value));
2671
3183
  };
3184
+ /**
3185
+ * Chuyển đổi dữ liệu Base64 thành Blob
3186
+ * @param dataBase64 Dữ liệu Base64 (có thể bao gồm marker ;base64,)
3187
+ * @returns Blob tương ứng
3188
+ */
2672
3189
  const convertBase64ToBlob = (dataBase64) => {
2673
3190
  const BASE64_MARKER = ';base64,';
2674
3191
  if (dataBase64.indexOf(BASE64_MARKER) === -1) {
@@ -2689,12 +3206,22 @@ const convertBase64ToBlob = (dataBase64) => {
2689
3206
  const blob = new Blob([uInt8Array], { type: contentType });
2690
3207
  return blob;
2691
3208
  };
3209
+ /**
3210
+ * Chuyển đổi File thành Base64 string hoặc Object URL tùy loại file
3211
+ * @param file Đối tượng File cần chuyển đổi
3212
+ * @returns Nếu là ảnh, trả về Base64 string. Ngược lại trả về Object URL.
3213
+ */
2692
3214
  const convertFileToBase64_ObjectUrl = async (file) => {
2693
3215
  if (!file.type.match(/image.*/)) {
2694
3216
  return URL.createObjectURL(file);
2695
3217
  }
2696
3218
  return await convertFileToBase64(file);
2697
3219
  };
3220
+ /**
3221
+ * Chuyển đổi File thành Base64 string
3222
+ * @param file Đối tượng File cần chuyển đổi
3223
+ * @returns Promise chứa chuỗi Base64
3224
+ */
2698
3225
  const convertFileToBase64 = (file) => {
2699
3226
  return new Promise((resolve) => {
2700
3227
  const reader = new FileReader();
@@ -2705,6 +3232,13 @@ const convertFileToBase64 = (file) => {
2705
3232
  });
2706
3233
  };
2707
3234
 
3235
+ /**
3236
+ * Tải file từ URL bằng XMLHttpRequest.
3237
+ * Khác với `downloadFileByUrl`, hàm này sử dụng XHR để tải file, hữu ích hơn
3238
+ * trong các trường hợp cần header tùy chỉnh hoặc gọi API nội bộ.
3239
+ * @param fileUrl URL của file cần tải
3240
+ * @param filename Tên file sẽ được lưu về máy
3241
+ */
2708
3242
  const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
2709
3243
  const xmlRequest = new XMLHttpRequest();
2710
3244
  xmlRequest.open('GET', fileUrl, true);
@@ -2719,6 +3253,13 @@ const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
2719
3253
  };
2720
3254
  xmlRequest.send();
2721
3255
  };
3256
+ /**
3257
+ * Tải file từ URL bằng cách tạo thẻ `<a>` ảo và kích hoạt sự kiện click.
3258
+ * Hỗ trợ chế độ mở file trên tab mới (onlyOpen) hoặc tải xuống thực sự.
3259
+ * @param fileUrl URL trực tiếp của file hoặc Blob URL
3260
+ * @param filename Tên file sẽ được lưu khi tải về
3261
+ * @param onlyOpen Nếu `true`, chỉ mở file trong tab mới thay vì tải về
3262
+ */
2722
3263
  const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
2723
3264
  const downloadFileElement = document.createElement('a');
2724
3265
  if (!onlyOpen) {
@@ -2734,6 +3275,13 @@ const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
2734
3275
  downloadFileElement.click();
2735
3276
  downloadFileElement.remove();
2736
3277
  };
3278
+ /**
3279
+ * Tải ảnh từ một phần tử `<img>` dựa trên thuộc tính `src` (hỗ trợ Base64 Data URL).
3280
+ * Thường dùng để cho phép người dùng lưu ảnh barcode, QR code về máy.
3281
+ * @param imageElement Phần tử `<img>` cần lấy ảnh
3282
+ * @param typeFileDownload Loại MIME của file (mặc định: 'image/png')
3283
+ * @param nameFile Tên file sẽ được lưu (mặc định: 'barcode')
3284
+ */
2737
3285
  const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
2738
3286
  const parentElement = imageElement?.src;
2739
3287
  const blobData = convertBase64ToBlob(parentElement);
@@ -2750,11 +3298,17 @@ const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN
2750
3298
  const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
2751
3299
  const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');
2752
3300
 
3301
+ /** Kiểm tra file có phải là ảnh không (image/*). */
2753
3302
  const isTypeImage = (file) => (file.type.match(/image.*/) ? true : false);
3303
+ /** Kiểm tra file có phải là video không (video/*). */
2754
3304
  const isTypeVideo = (file) => (file.type.match(/video.*/) ? true : false);
3305
+ /** Kiểm tra file có phải là audio không (audio/*). */
2755
3306
  const isTypeAudio = (file) => (file.type.match(/audio.*/) ? true : false);
3307
+ /** Kiểm tra giá trị có phải là đối tượng File không. */
2756
3308
  const isTypeFile = (file) => (file instanceof File || Object.prototype.toString.call(file) === '[object File]' ? true : false);
3309
+ /** Danh sách extension/MIME type của file Excel. */
2757
3310
  const ExcelExtList = ['xls', 'xlsx', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
3311
+ /** Danh sách extension/MIME type của tài liệu (Word, Excel, PDF, JSON...). */
2758
3312
  const DocumentExtList = [
2759
3313
  'doc',
2760
3314
  'docx',
@@ -2775,13 +3329,40 @@ const DocumentExtList = [
2775
3329
  'application/json',
2776
3330
  'application/xml',
2777
3331
  ];
3332
+ /**
3333
+ * Kiểm tra extension có thuộc danh sách tài liệu được chấp nhận không.
3334
+ * @param ext Extension hoặc MIME type cần kiểm tra
3335
+ * @param listExt Danh sách tùy chỉnh (mặc định là DocumentExtList)
3336
+ */
2778
3337
  const isIncludeDocumentExtList = (ext, listExt = DocumentExtList) => listExt.includes(ext) || listExt.includes(`application/${ext}`);
3338
+ /** Danh sách extension/MIME type của ảnh. */
2779
3339
  const ImageExtList = ['gif', 'jpg', 'jpeg', 'png', 'image/gif', 'image/jpeg', 'image/jpeg', 'image/png', 'webp'];
3340
+ /**
3341
+ * Kiểm tra extension có thuộc danh sách ảnh không.
3342
+ * @param ext Extension hoặc MIME type cần kiểm tra
3343
+ * @param listExt Danh sách tùy chỉnh (mặc định là ImageExtList)
3344
+ */
2780
3345
  const isIncludeImageExtList = (ext, listExt = ImageExtList) => listExt.includes(ext);
3346
+ /** Danh sách extension/MIME type của video. */
2781
3347
  const VideoExtList = ['mp4', 'mov', 'mpg', 'avi', 'wmv', 'video/mp4', 'video/quicktime', 'video/mpeg', 'video/x-msvideo', 'video/x-ms-wmv'];
3348
+ /**
3349
+ * Kiểm tra extension có thuộc danh sách video không.
3350
+ * @param ext Extension hoặc MIME type cần kiểm tra
3351
+ */
2782
3352
  const isIncludeVideoExtList = (ext, listExt = VideoExtList) => listExt.includes(ext);
3353
+ /** Danh sách extension/MIME type của audio. */
2783
3354
  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'];
3355
+ /**
3356
+ * Kiểm tra extension có thuộc danh sách audio không.
3357
+ * @param ext Extension hoặc MIME type cần kiểm tra
3358
+ */
2784
3359
  const isIncludeAudioExtList = (ext, listExt = AudioExtList) => listExt.includes(ext);
3360
+ /**
3361
+ * Lấy phần mở rộng (extension) của một file.
3362
+ * Hỗ trợ cả đối tượng File gốc của Browser và IFile (interface nội bộ).
3363
+ * @param file Đối tượng File hoặc IFile
3364
+ * @returns Extension dạng chữ thường (vd: "jpg", "pdf") hoặc undefined
3365
+ */
2785
3366
  const getFileExtension = (file) => {
2786
3367
  if (file instanceof File && file.type) {
2787
3368
  return (file.type.split('/').pop() || '').toLowerCase();
@@ -2805,17 +3386,37 @@ const getFileExtension = (file) => {
2805
3386
  }
2806
3387
  return (dots.pop() || '').toLowerCase();
2807
3388
  };
3389
+ /**
3390
+ * Định dạng kích thước file dạng chuỗi dễ đọc (KB hoặc MB).
3391
+ * @param size Kích thước file tính bằng byte
3392
+ * @param toFixed Số chữ số thập phân (mặc định: 2)
3393
+ * @returns Chuỗi định dạng (vd: " 1.50 MB", " 512.00 KB")
3394
+ */
2808
3395
  const getLabelBySizeFile = (size, toFixed = 2) => {
2809
3396
  if (size < 1024 * 1024) {
2810
3397
  return ` ${(size / 1024).toFixed(toFixed)} KB`;
2811
3398
  }
2812
3399
  return ` ${(size / (1024 * 1024)).toFixed(toFixed)} MB`;
2813
3400
  };
3401
+ /**
3402
+ * Chuyển đổi đối tượng Blob thành File.
3403
+ * Tự động xác định phần mở rộng từ MIME type của Blob.
3404
+ * @param blob Đối tượng Blob cần chuyển
3405
+ * @param fileName Tên file mong muốn (mặc định: UUID ngẫu nhiên)
3406
+ * @returns Đối tượng File tương ứng
3407
+ */
2814
3408
  const convertBlobToFile = (blob, fileName) => {
2815
3409
  const type = blob.type.split('/')[1];
2816
3410
  const name = fileName ? `${fileName.split('.')[0]}.${type}` : `${uuid()}.${type}`;
2817
3411
  return new File([blob], name, { type: blob.type, lastModified: Date.now() });
2818
3412
  };
3413
+ /**
3414
+ * Tải file từ URL và chuyển về đối tượng File.
3415
+ * Sử dụng XHR + FileReader để đọc nội dung, sau đó chuyển Base64 → Blob → File.
3416
+ * @param url URL của file cần tải
3417
+ * @param fileName Tên file mong muốn (mặc định: timestamp)
3418
+ * @returns Promise trả về File hoặc undefined nếu thất bại
3419
+ */
2819
3420
  const convertUrlToFile = (url, fileName) => {
2820
3421
  return new Promise((resolve, reject) => {
2821
3422
  const xhr = new XMLHttpRequest();
@@ -2837,6 +3438,16 @@ const convertUrlToFile = (url, fileName) => {
2837
3438
  });
2838
3439
  };
2839
3440
 
3441
+ /**
3442
+ * Highlight một từ khóa tìm kiếm bên trong chuỗi văn bản bằng cách bọc vào thẻ `<span>`.
3443
+ * Hỗ trợ tìm kiếm không phân biệt dấu (Unicode-insensitive) và không phân biệt hoa/thường.
3444
+ *
3445
+ * @param value Chuỗi văn bản gốc cần highlight
3446
+ * @param search Từ khóa cần tìm và highlight
3447
+ * @param ignoreHighlight Nếu `true`, bỏ qua highlight và trả về chuỗi gốc
3448
+ * @param classHightLight Class CSS áp dụng cho span highlight (mặc định: 'bg-[#19344a] text-white')
3449
+ * @returns Chuỗi HTML đã được inject thẻ `<span>` quanh từ khóa
3450
+ */
2840
3451
  const highlightByKeyword = (value, search, ignoreHighlight, classHightLight) => {
2841
3452
  if (!value) {
2842
3453
  return CHARACTER_DATA_EMPTY;
@@ -2877,6 +3488,14 @@ const getValueReplace = (search, value) => {
2877
3488
  }
2878
3489
  return Array.from(keys);
2879
3490
  };
3491
+ /**
3492
+ * Chuẩn hóa chuỗi văn bản theo chuẩn Unicode NFC và áp dụng các tùy chọn format.
3493
+ * Thường dùng để so sánh chuỗi nhất quán, không phụ thuộc vào cách gõ có dấu.
3494
+ *
3495
+ * @param text Chuỗi cần chuẩn hóa
3496
+ * @param options Các tùy chọn transform (trim, removeEmoji, removeUnicode...)
3497
+ * @returns Chuỗi đã chuẩn hóa
3498
+ */
2880
3499
  const formatTextCompare = (text, options) => {
2881
3500
  if (!text) {
2882
3501
  return text;
@@ -2884,12 +3503,25 @@ const formatTextCompare = (text, options) => {
2884
3503
  text = text.normalize('NFC');
2885
3504
  return formatByOptions(text, options);
2886
3505
  };
3506
+ /**
3507
+ * Định dạng họ tên đầy đủ: viết hoa chữ cái đầu mỗi từ, xóa khoảng trắng thừa và emoji.
3508
+ * @param value Chuỗi họ tên cần format
3509
+ * @returns Chuỗi đã được chuẩn hóa
3510
+ * @example fullNameFormat(' nguyễn văn a ') → 'Nguyễn Văn A'
3511
+ */
2887
3512
  const fullNameFormat = (value) => {
2888
3513
  if (!value) {
2889
3514
  return value;
2890
3515
  }
2891
3516
  return capitalize(value, { lowercaseOtherCharacter: true, trim: true, removeMultipleSpace: true, removeEmoji: true });
2892
3517
  };
3518
+ /**
3519
+ * Viết hoa chữ cái đầu tiên của mỗi từ trong chuỗi.
3520
+ * @param text Chuỗi cần capitalize
3521
+ * @param options Tùy chọn xử lý trước (lowercase, trim, removeEmoji...)
3522
+ * @returns Chuỗi đã được capitalize theo từng từ
3523
+ * @example capitalize('hello world') → 'Hello World'
3524
+ */
2893
3525
  const capitalize = (text, options) => {
2894
3526
  if (!text) {
2895
3527
  return text;
@@ -2900,12 +3532,26 @@ const capitalize = (text, options) => {
2900
3532
  .map((word) => firstLetterToUpperCase(word))
2901
3533
  .join(' ');
2902
3534
  };
3535
+ /**
3536
+ * Viết hoa chữ cái đầu tiên của chuỗi.
3537
+ * @param text Chuỗi cần xử lý
3538
+ * @param options Tùy chọn format bổ sung
3539
+ * @returns Chuỗi với chữ cái đầu tiên được viết hoa
3540
+ * @example firstLetterToUpperCase('hello') → 'Hello'
3541
+ */
2903
3542
  const firstLetterToUpperCase = (text, options) => {
2904
3543
  if (!text) {
2905
3544
  return text;
2906
3545
  }
2907
3546
  return uppercaseByPosition(text, 0, options);
2908
3547
  };
3548
+ /**
3549
+ * Viết hoa một ký tự tại vị trí bất kỳ trong chuỗi.
3550
+ * @param text Chuỗi cần xử lý
3551
+ * @param position Vị trí ký tự cần viết hoa (0-indexed)
3552
+ * @param options Tùy chọn format bổ sung
3553
+ * @returns Chuỗi với ký tự tại vị trí được viết hoa
3554
+ */
2909
3555
  const uppercaseByPosition = (text, position, options) => {
2910
3556
  if (!text) {
2911
3557
  return text;
@@ -2938,12 +3584,25 @@ const formatByOptions = (text, options) => {
2938
3584
  }
2939
3585
  return text;
2940
3586
  };
3587
+ /**
3588
+ * Escape các ký tự đặc biệt HTML thành HTML entities để tránh XSS.
3589
+ * @param str Chuỗi cần escape
3590
+ * @returns Chuỗi an toàn (& → &amp;, < → &lt;, > → &gt;, " → &quot;, ' → &#039;)
3591
+ * @example escapeHtml('<script>') → '&lt;script&gt;'
3592
+ */
2941
3593
  const escapeHtml = (str) => {
2942
3594
  if (!str || typeof str !== 'string') {
2943
3595
  return str;
2944
3596
  }
2945
3597
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
2946
3598
  };
3599
+ /**
3600
+ * Giải mã chuỗi HTML entities về dạng ký tự gốc.
3601
+ * Sử dụng thẻ textarea để giải mã an toàn, hỗ trợ multi-level encoding.
3602
+ * @param str Chuỗi HTML encoded cần giải mã
3603
+ * @returns Chuỗi gốc sau khi decode
3604
+ * @example decodeEscapeHtml('&lt;b&gt;') → '<b>'
3605
+ */
2947
3606
  const decodeEscapeHtml = (str) => {
2948
3607
  const htmlTag = document.createElement('textarea');
2949
3608
  htmlTag.innerHTML = str;
@@ -2958,9 +3617,15 @@ const decodeEscapeHtml = (str) => {
2958
3617
  }
2959
3618
  return str;
2960
3619
  };
3620
+ /**
3621
+ * Xóa dấu tiếng Việt khỏi chuỗi, chuyển tất cả ký tự có dấu về dạng Latin cơ bản.
3622
+ * Hỗ trợ cả chữ thường lẫn hoa.
3623
+ * @param str Chuỗi tiếng Việt cần xóa dấu
3624
+ * @returns Chuỗi đã xóa dấu (vd: 'Nguyễn Văn An' → 'Nguyen Van An')
3625
+ */
2961
3626
  const deleteUnicode = (str) => {
2962
3627
  str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
2963
- str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ|ễ/g, 'e');
3628
+ str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ|ễ/g, 'e');
2964
3629
  str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
2965
3630
  str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
2966
3631
  str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
@@ -2977,6 +3642,13 @@ const deleteUnicode = (str) => {
2977
3642
  str = str.replace(/\u02C6|\u0306|\u031B/g, ''); // ˆ ̆ ̛ Â, Ê, Ă, Ơ, Ư
2978
3643
  return str.normalize('NFC');
2979
3644
  };
3645
+ /**
3646
+ * Xóa tất cả emoji khỏi chuỗi văn bản.
3647
+ * Sử dụng pattern regex từ `patternEmoji()` để nhận diện toàn bộ emoji Unicode.
3648
+ * @param text Chuỗi cần xóa emoji
3649
+ * @returns Chuỗi đã được xóa emoji
3650
+ * @example removeEmoji('Hello 😀 World 🎉') → 'Hello World '
3651
+ */
2980
3652
  const removeEmoji = (text) => {
2981
3653
  if (!text || !text.trim()) {
2982
3654
  return text;
@@ -3199,12 +3871,24 @@ const createUniqueRandomIntGenerator = (min, max) => {
3199
3871
  };
3200
3872
  };
3201
3873
 
3874
+ /**
3875
+ * Thêm tất cả phần tử trong mảng vào một Set
3876
+ * @param setStore Đối tượng Set mục tiêu
3877
+ * @param data Mảng các phần tử cần thêm
3878
+ * @returns Set sau khi đã thêm dữ liệu
3879
+ */
3202
3880
  const addArrayToSet = (setStore, data) => {
3203
3881
  for (const value of data) {
3204
3882
  setStore.add(value);
3205
3883
  }
3206
3884
  return setStore;
3207
3885
  };
3886
+ /**
3887
+ * Chuyển đổi Set sang Array, có hỗ trợ mapping function
3888
+ * @param data Đối tượng Set cần chuyển đổi
3889
+ * @param functionMap Hàm mapping tùy chọn để biến đổi từng phần tử
3890
+ * @returns Mảng kết quả
3891
+ */
3208
3892
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3209
3893
  const convertSetToArray = (data, functionMap) => {
3210
3894
  if (functionMap) {
@@ -3214,9 +3898,11 @@ const convertSetToArray = (data, functionMap) => {
3214
3898
  };
3215
3899
 
3216
3900
  /**
3217
- * Tính dung lượng (ước tính) của 1 object trong bộ nhớ (bytes, KB, MB)
3218
- * @param obj - Bất kỳ kiểu dữ liệu nào (object, array, map, set, primitive,...)
3219
- * @param formatted - Nếu true, trả về chuỗi có đơn vị (vd: "2.35 KB")
3901
+ * Tính toán dung lượng (ước tính) của một đối tượng trong bộ nhớ.
3902
+ * Hàm sử dụng JSON.stringify Blob để tính toán kích thước thực tế theo byte.
3903
+ *
3904
+ * @param obj Đối tượng cần tính dung lượng (Object, Array, Primitive...)
3905
+ * @returns Chuỗi mô tả dung lượng đã được format (ví dụ: "100 bytes", "2.50 KB", "1.20 MB"). Trả về "N/A" nếu không thể serialize.
3220
3906
  */
3221
3907
  const getObjectSize = (obj) => {
3222
3908
  try {
@@ -3236,6 +3922,15 @@ const getObjectSize = (obj) => {
3236
3922
  }
3237
3923
  };
3238
3924
 
3925
+ /**
3926
+ * Chuẩn hóa một URL bằng cách loại bỏ các dấu gạch chéo kép (//) trong pathname.
3927
+ *
3928
+ * @param rawUrl - URL cần chuẩn hóa.
3929
+ * @returns URL đã được chuẩn hóa dưới dạng chuỗi.
3930
+ *
3931
+ * @example
3932
+ * normalizeUrl('https://example.com//path//to//file') // => 'https://example.com/path/to/file'
3933
+ */
3239
3934
  const normalizeUrl = (rawUrl) => {
3240
3935
  const url = new URL(rawUrl);
3241
3936
  url.pathname = url.pathname.replace(/\/{2,}/g, '/');
@@ -3265,6 +3960,13 @@ const ignorePatterns = [
3265
3960
  /vite/,
3266
3961
  /node_modules/,
3267
3962
  ];
3963
+ /**
3964
+ * Trả về danh sách tên các hàm trong stack trace hiện tại.
3965
+ * Đã lọc bỏ các hàm nội bộ của Angular, RxJS, Zone.js và các thư viện hệ thống.
3966
+ * Hữu ích cho việc logging hoặc debugging để biết source gọi đến function hiện tại.
3967
+ *
3968
+ * @returns {string[]} Mảng chứa tên các hàm theo thứ tự từ gốc đến ngọn (ngược callback stack).
3969
+ */
3268
3970
  const traceStack = () => {
3269
3971
  const stack = new Error().stack;
3270
3972
  if (!stack)
@@ -3296,5 +3998,5 @@ const traceStack = () => {
3296
3998
  * Generated bundle index. Do not edit.
3297
3999
  */
3298
4000
 
3299
- 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, addArrayToSet, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertObjectToSignal, convertSetToArray, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, detectAndCleanNearWhiteColors, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getKeyCacheByArrayObject, getLabelBySizeFile, getObjectSize, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNearWhite, isNil, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTouchDevice, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, normalizeUrl, omitBy, patterProtocolUrl, patternAccount, patternDomain, patternEmail, patternEmoji, patternEncodeUri, patternGetFieldByRuleFieldReplace, patternHostUrl, patternKey, patternMobilePhone, patternName, patternNameProfile, patternNameSpecial, patternNameUtf8, patternNumber, patternPem, patternPhone, patternRuleFieldReplace, patternTax, patternUrl, protectString, range, removeEmoji, revealString, rgbToHex, set, setCaretPosition, setDefaultTimeZone, setKeyCrypto, setKeyCrypto3rd, setPatternCheckTimeUTC, setStylesElement, traceStack, uniqBy, unwrapSignal, updateFunctionCheckEmbedFrame, updateFunctionFormatDate, updateFunctionXssFilter, uppercaseByPosition, uuid, viewDataNumberByLanguage, xssFilter };
4001
+ 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, addArrayToSet, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertObjectToSignal, convertSetToArray, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, detectAndCleanNearWhiteColors, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getKeyCacheByArrayObject, getLabelBySizeFile, getObjectSize, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNearWhite, isNil, isPrimitiveType, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTouchDevice, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, normalizeUrl, omitBy, patternAccount, patternDomain, patternEmail, patternEmoji, patternEncodeUri, patternGetFieldByRuleFieldReplace, patternHostUrl, patternKey, patternMobilePhone, patternName, patternNameProfile, patternNameSpecial, patternNameUtf8, patternNumber, patternPem, patternPhone, patternProtocolUrl, patternRuleFieldReplace, patternTax, patternUrl, protectString, range, removeEmoji, revealString, rgbToHex, set, setCaretPosition, setDefaultTimeZone, setKeyCrypto, setKeyCrypto3rd, setPatternCheckTimeUTC, setStylesElement, traceStack, uniqBy, unwrapSignal, updateFunctionCheckEmbedFrame, updateFunctionFormatDate, updateFunctionXssFilter, uppercaseByPosition, uuid, viewDataNumberByLanguage, xssFilter };
3300
4002
  //# sourceMappingURL=libs-ui-utils.mjs.map