@gateweb/react-utils 1.17.0 → 2.1.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.
package/dist/cjs/index.js CHANGED
@@ -1,17 +1,905 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
- var webStorage12s = require('./webStorage-12s-P8HpaTRP.js');
4
3
  var dayjs = require('dayjs');
5
- var queryStore12s = require('./queryStore-12s-q_SLGgYH.js');
6
- var React = require('react');
7
- var useCountdown12s = require('./useCountdown-12s-uiqhgllY.js');
8
- var useDisclosure12s = require('./useDisclosure-12s-SZtbSE4A.js');
9
- var download12s = require('./download-12s-DKxkL92w.js');
10
4
 
11
5
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
6
 
13
7
  var dayjs__default = /*#__PURE__*/_interopDefault(dayjs);
14
- var React__default = /*#__PURE__*/_interopDefault(React);
8
+
9
+ /* eslint-disable no-bitwise */ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
10
+ /**
11
+ * 將位元組陣列編碼為 base64 字串
12
+ *
13
+ * @param bytes 要編碼的位元組陣列
14
+ * @returns base64 編碼的字串
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const bytes = new TextEncoder().encode('Hello World');
19
+ * const encoded = encodeBase64(bytes);
20
+ * console.log(encoded); // 'SGVsbG8gV29ybGQ='
21
+ * ```
22
+ */ const encodeBase64 = (bytes)=>{
23
+ let out = '';
24
+ for(let i = 0; i < bytes.length; i += 3){
25
+ const n = bytes[i] << 16 | (bytes[i + 1] || 0) << 8 | (bytes[i + 2] || 0);
26
+ out += chars[n >> 18 & 63] + chars[n >> 12 & 63] + (i + 1 < bytes.length ? chars[n >> 6 & 63] : '=') + (i + 2 < bytes.length ? chars[n & 63] : '=');
27
+ }
28
+ return out;
29
+ };
30
+ /**
31
+ * 將 base64 字串解碼為位元組陣列
32
+ *
33
+ * @param base64 要解碼的 base64 字串(支援 base64url 格式)
34
+ * @returns 解碼後的位元組陣列
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const decoded = decodeBase64('SGVsbG8gV29ybGQ=');
39
+ * const text = new TextDecoder().decode(decoded);
40
+ * console.log(text); // 'Hello World'
41
+ * ```
42
+ */ const decodeBase64 = (base64)=>{
43
+ let out = base64.replace(/-/g, '+').replace(/_/g, '/'); // 支援 base64url
44
+ while(out.length % 4)out += '='; // 自動補 "="
45
+ const buffer = [];
46
+ for(let i = 0; i < out.length; i += 4){
47
+ const n = chars.indexOf(out[i]) << 18 | chars.indexOf(out[i + 1]) << 12 | (chars.indexOf(out[i + 2]) & 63) << 6 | chars.indexOf(out[i + 3]) & 63;
48
+ buffer.push(n >> 16 & 255);
49
+ if (out[i + 2] !== '=') buffer.push(n >> 8 & 255);
50
+ if (out[i + 3] !== '=') buffer.push(n & 255);
51
+ }
52
+ return new Uint8Array(buffer);
53
+ };
54
+ /**
55
+ * 將 JSON 資料編碼為 base64 字串
56
+ *
57
+ * @param data 要編碼的 JSON 可序列化資料
58
+ * @returns base64 編碼的字串
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const data = { name: 'John', age: 30 };
63
+ * const encoded = encodeJson(data);
64
+ * console.log(encoded); // 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9'
65
+ *
66
+ * const array = [1, 2, 3];
67
+ * const encodedArray = encodeJson(array);
68
+ * ```
69
+ */ const encodeJson = (data)=>{
70
+ const json = JSON.stringify(data);
71
+ return encodeBase64(new TextEncoder().encode(json));
72
+ };
73
+ /**
74
+ * 將 base64 字串解碼為 JSON 資料
75
+ *
76
+ * @param b64 要解碼的 base64 字串
77
+ * @returns 解碼後的資料
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const encoded = 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9';
82
+ * const decoded = decodeJson<{ name: string; age: number }>(encoded);
83
+ * console.log(decoded); // { name: 'John', age: 30 }
84
+ *
85
+ * const decodedArray = decodeJson<number[]>(encodedArray);
86
+ * ```
87
+ */ const decodeJson = (b64)=>{
88
+ const json = new TextDecoder().decode(decodeBase64(b64));
89
+ return JSON.parse(json);
90
+ };
91
+
92
+ const FILE_SIZE_UNITS$1 = [
93
+ 'Bytes',
94
+ 'KB',
95
+ 'MB',
96
+ 'GB',
97
+ 'TB',
98
+ 'PB',
99
+ 'EB',
100
+ 'ZB',
101
+ 'YB'
102
+ ];
103
+ /**
104
+ * 代表字節大小的類,提供各種格式化和轉換方法
105
+ */ class ByteSize {
106
+ /**
107
+ * 建立一個新的 ByteSize 實例
108
+ * @param bytes 位元組數值
109
+ */ constructor(bytes){
110
+ this.bytes = bytes;
111
+ }
112
+ /**
113
+ * 取得原始位元組數值
114
+ */ get value() {
115
+ return this.bytes;
116
+ }
117
+ /**
118
+ * 將位元組轉換為指定單位的數值
119
+ *
120
+ * @param unit 目標單位 (例如 'KB', 'MB', 'GB')
121
+ * @returns 轉換後的數值
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const size = createByteSize(1024);
126
+ * size.to('KB'); // 1
127
+ * size.to('MB'); // 0.0009765625
128
+ * ```
129
+ */ to(unit) {
130
+ if (!FILE_SIZE_UNITS$1.includes(unit)) {
131
+ console.warn(`Invalid unit: ${unit}. Valid units are: ${FILE_SIZE_UNITS$1.join(', ')}`);
132
+ return this.bytes;
133
+ }
134
+ const k = 1024;
135
+ const i = FILE_SIZE_UNITS$1.indexOf(unit);
136
+ return this.bytes / k ** i;
137
+ }
138
+ /**
139
+ * 轉換為適當單位的可讀字符串
140
+ *
141
+ * @param unit 指定單位 (例如 'KB', 'MB', 'GB'),不指定則自動選擇最適合的單位
142
+ * @param decimals 小數點位數 (預設為 2)
143
+ * @returns 格式化後的字符串
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const size = createByteSize(1024);
148
+ * size.format(); // '1.00 KB'
149
+ * size.format('KB'); // '1.00 KB'
150
+ * size.format('MB', 3); // '0.001 MB'
151
+ * ```
152
+ */ format(unit, decimals = 2) {
153
+ if (this.bytes === 0) return `0 ${unit || 'Bytes'}`;
154
+ const k = 1024;
155
+ // 如果指定了有效單位,則使用;否則,計算最適合的單位
156
+ const i = unit && FILE_SIZE_UNITS$1.includes(unit) ? FILE_SIZE_UNITS$1.indexOf(unit) : Math.floor(Math.log(this.bytes) / Math.log(k));
157
+ return `${(this.bytes / k ** i).toFixed(decimals)} ${FILE_SIZE_UNITS$1[i]}`;
158
+ }
159
+ /**
160
+ * 比較兩個 ByteSize 實例
161
+ *
162
+ * @param other 要比較的另一個 ByteSize 實例
163
+ * @returns 比較結果:-1 表示小於,0 表示等於,1 表示大於
164
+ */ compareTo(other) {
165
+ if (this.bytes < other.value) return -1;
166
+ if (this.bytes > other.value) return 1;
167
+ return 0;
168
+ }
169
+ /**
170
+ * 轉換為字符串表示
171
+ * @returns 預設格式化的字符串
172
+ */ toString() {
173
+ return this.format();
174
+ }
175
+ }
176
+ /**
177
+ * 將位元組轉換為可讀字符串 (保留舊的 API 以確保向後兼容)
178
+ *
179
+ * @param bytes 位元組數值
180
+ * @param unit 目標單位 (例如 'KB', 'MB', 'GB'),不指定則自動選擇最適合的單位
181
+ * @param decimals 小數點位數 (預設為 2)
182
+ * @returns 格式化後的字符串
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * convertBytes(0) // '0 Bytes'
187
+ * convertBytes(1024, 'KB') // '1 KB'
188
+ * convertBytes(1024, 'KB', 2) // '1.00 KB'
189
+ * convertBytes(1024 * 1024, 'MB') // '1 MB'
190
+ * convertBytes(1024 * 1024, 'KB') // '1024 KB'
191
+ * ```
192
+ */ const convertBytes = (bytes, unit, decimals = 2)=>new ByteSize(bytes).format(unit, decimals);
193
+
194
+ /**
195
+ * 檢查兩個值是否相等(包含陣列等引用型別)
196
+ * - 支援基本型別的直接比較
197
+ * - 特別處理空陣列比較
198
+ * - 支援非空陣列的深度比較
199
+ *
200
+ * @param value1 - 第一個值
201
+ * @param value2 - 第二個值
202
+ * @returns 如果值相等則返回 true
203
+ *
204
+ * @example
205
+ * // 基本型別比較
206
+ * isEqual(1, 1); // true
207
+ * isEqual('abc', 'abc'); // true
208
+ * isEqual(null, null); // true
209
+ *
210
+ * // 陣列比較
211
+ * isEqual([], []); // true
212
+ * isEqual([1, 2], [1, 2]); // true
213
+ * isEqual([1, 2], [1, 3]); // false
214
+ */ const isEqual = (value1, value2)=>{
215
+ // 處理基本型別
216
+ if (value1 === value2) {
217
+ return true;
218
+ }
219
+ // 處理空陣列
220
+ if (Array.isArray(value1) && Array.isArray(value2) && value1.length === 0 && value2.length === 0) {
221
+ return true;
222
+ }
223
+ // 兩者都是非 null 的物件(包含陣列)
224
+ if (value1 !== null && value2 !== null && typeof value1 === 'object' && typeof value2 === 'object') {
225
+ // 處理非空陣列
226
+ if (Array.isArray(value1) && Array.isArray(value2)) {
227
+ if (value1.length !== value2.length) {
228
+ return false;
229
+ }
230
+ return value1.every((item, index)=>isEqual(item, value2[index]));
231
+ }
232
+ // 未來可以擴展這裡,增加其他物件型別的深度比較
233
+ }
234
+ return false;
235
+ };
236
+
237
+ /**
238
+ * 將指定格式的物件轉換成類似 enum 的物件
239
+ *
240
+ * @param enumObject enum 物件
241
+ * @param valueKey value 的 key
242
+ *
243
+ * @example
244
+ * const myObj = {
245
+ * A: { value: 'a' },
246
+ * B: { value: 'b', other: 'other' },
247
+ * } as const;
248
+ * const enumCode = extractEnumLikeObject(myObj, 'value');
249
+ * // => { A: 'a', B: 'b' }
250
+ */ const extractEnumLikeObject = (enumObject, valueKey)=>Object.entries(enumObject).reduce((acc, [key, value])=>({
251
+ ...acc,
252
+ [key]: value[valueKey]
253
+ }), {});
254
+ // 實作
255
+ function createEnumLikeObject(obj, options) {
256
+ const name = options?.name;
257
+ const scene = options?.scene;
258
+ const valueKey = options?.valueKey ?? 'value';
259
+ const Enum = extractEnumLikeObject(obj, valueKey);
260
+ // List:根據有無 scene 做不同處理
261
+ let list;
262
+ if (scene) {
263
+ // scenes 版本:解構指定 scene 的 label
264
+ list = Object.entries(obj).map(([key, item])=>({
265
+ key,
266
+ value: item?.[valueKey],
267
+ ...item.scenes?.[scene] ?? {}
268
+ }));
269
+ } else {
270
+ // label only 版本
271
+ list = Object.entries(obj).map(([key, item])=>({
272
+ key,
273
+ value: item?.[valueKey],
274
+ ...item
275
+ }));
276
+ }
277
+ function getLabel(value) {
278
+ const targetItem = list.find((item)=>// 如果 list 項目內含有 valueKey 對應欄位,優先以該欄位比對;否則以 value 欄位比對
279
+ valueKey in item ? item[valueKey] === value : item.value === value);
280
+ if (!targetItem) return String(value);
281
+ if ('label' in targetItem) return targetItem.label;
282
+ return String(value);
283
+ }
284
+ if (name) {
285
+ // 命名版:使用 Enum${name}, ${name}List, get${name}Label 鍵名
286
+ return {
287
+ [`Enum${name}`]: Enum,
288
+ [`${name}List`]: list,
289
+ [`get${name}Label`]: getLabel
290
+ };
291
+ }
292
+ // 未命名:提供預設鍵名 Enum, List, getLabel
293
+ return {
294
+ Enum,
295
+ List: list,
296
+ getLabel
297
+ };
298
+ }
299
+
300
+ /**
301
+ * simulate a fake api request
302
+ *
303
+ * @param returnValue the value to return
304
+ * @param result the result of the request
305
+ * @param time the time to wait before resolving
306
+ *
307
+ * @example
308
+ *
309
+ * const result = await fakeApi({ foo: 'bar' });
310
+ * console.log(result); // { result: true, data: { foo: 'bar' } }
311
+ */ const fakeApi = (returnValue, result = true, time = 1000)=>new Promise((resolve)=>{
312
+ setTimeout(()=>{
313
+ if (result) resolve({
314
+ result,
315
+ data: returnValue
316
+ });
317
+ else resolve({
318
+ result,
319
+ message: 'error'
320
+ });
321
+ }, time);
322
+ });
323
+
324
+ const MimeTypeMap = {
325
+ jpeg: 'image/jpeg',
326
+ jpg: 'image/jpeg',
327
+ gif: 'image/gif',
328
+ png: 'image/png',
329
+ pdf: 'application/pdf',
330
+ zip: 'application/zip',
331
+ csv: 'text/csv',
332
+ ppt: 'application/vnd.ms-powerpoint',
333
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
334
+ xls: 'application/vnd.ms-excel',
335
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
336
+ doc: 'application/msword',
337
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
338
+ txt: 'text/plain'
339
+ };
340
+ const OtherMimeType = 'application/octet-stream';
341
+
342
+ /**
343
+ * 檢查檔案是否為合法的檔案類型
344
+ *
345
+ * `accepts` 可同時接受副檔名以及 MIME 類型
346
+ *
347
+ * @param file 檔案
348
+ * @param accepts 允許的類型
349
+ *
350
+ * @example
351
+ *
352
+ * ```js
353
+ * validateFileType({ type: 'image/png' }, ['image/png', 'image/jpeg']) // true
354
+ * validateFileType({ type: 'image/png' }, ['image/jpeg']) // false
355
+ * validateFileType({ type: 'image/png' }, ['image/*']) // true
356
+ * validateFileType({ name: '圖片.png', type: 'image/png' }, ['.png']) // true
357
+ * ```
358
+ */ const validateFileType = (file, accepts)=>{
359
+ if (accepts.length === 0) return true;
360
+ // 獲取文件的MIME類型
361
+ const fileMimeType = file.type;
362
+ // 提取副檔名(含 .,且轉小寫)
363
+ const fileExt = file.name.includes('.') ? `.${file.name.split('.').pop().toLowerCase()}` : '';
364
+ return accepts.some((accept)=>{
365
+ if (accept.startsWith('.')) {
366
+ // 以副檔名檢查,忽略大小寫
367
+ return fileExt === accept.toLowerCase();
368
+ }
369
+ if (accept === fileMimeType) {
370
+ return true;
371
+ }
372
+ if (accept.endsWith('/*')) {
373
+ return accept.split('/')[0] === fileMimeType.split('/')[0];
374
+ }
375
+ return false;
376
+ });
377
+ };
378
+ /**
379
+ * 根據檔案副檔名取得對應的 MIME Type
380
+ *
381
+ * 目前支援的副檔名有:pdf, csv, jpeg, jpg, png, zip, txt
382
+ * 除此之外的副檔名皆回傳 application/octet-stream
383
+ *
384
+ * @param fileExtension 檔案副檔名
385
+ *
386
+ * @example
387
+ *
388
+ * getMimeType('pdf') // 'application/pdf'
389
+ * getMimeType('csv') // 'text/csv'
390
+ * getMimeType('jpeg') // 'image/jpeg'
391
+ * getMimeType('jpg') // 'image/jpeg'
392
+ * getMimeType('png') // 'image/png'
393
+ * getMimeType('txt') // 'text/plain'
394
+ * getMimeType('zip') // 'application/zip'
395
+ * getMimeType('mp4') // 'application/octet-stream'
396
+ * getMimeType('xls') // 'application/vnd.ms-excel'
397
+ * getMimeType('xlsx') // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
398
+ * getMimeType('doc') // 'application/msword'
399
+ * getMimeType('docx') // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
400
+ *
401
+ * @deprecated use `parseFileInfoFromFilename` instead
402
+ */ const getMimeType = (fileName)=>{
403
+ const ext = (fileName.split('.').pop() || '').toLowerCase();
404
+ return MimeTypeMap[ext] ?? OtherMimeType;
405
+ };
406
+ /**
407
+ * 用來解析後端在 response header content-disposition 的內容
408
+ *
409
+ * 一般來說格式會有以下兩種
410
+ *
411
+ * - Content-Disposition: attachment; filename="file name.jpg"
412
+ * - Content-Disposition: attachment; filename*=UTF-8''file%20name2.jpg
413
+ *
414
+ * 如果格式正確就會取得檔案名稱(包含副檔名),優先取 filename* 內的內容
415
+ *
416
+ * @param disposition Content-Disposition
417
+ *
418
+ * @example
419
+ *
420
+ * parseFilenameFromDisposition('attachment; filename="file name1.jpg') // file name.jpg
421
+ * parseFilenameFromDisposition('attachment; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
422
+ * parseFilenameFromDisposition('attachment; filename="file name.jpg; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
423
+ */ const parseFilenameFromDisposition = (disposition)=>{
424
+ // 1. 先找 filename*
425
+ const filenameStarMatch = disposition.match(/filename\*\s*=\s*(?:UTF-8'')?([^;]+)/i);
426
+ if (filenameStarMatch && filenameStarMatch[1]) {
427
+ // 依 RFC 5987 格式(UTF-8''URL-ENCODED),要先 decode
428
+ try {
429
+ return decodeURIComponent(filenameStarMatch[1].replace(/(^['"]|['"]$)/g, ''));
430
+ } catch {
431
+ // fallback,如果 decode 失敗,直接傳回原值
432
+ return filenameStarMatch[1];
433
+ }
434
+ }
435
+ // 2. 沒有 filename*,再找 filename
436
+ const filenameMatch = disposition.match(/filename\s*=\s*("?)([^";]+)\1/i);
437
+ if (filenameMatch && filenameMatch[2]) {
438
+ return filenameMatch[2];
439
+ }
440
+ // 3. 都沒有則 undefined
441
+ return undefined;
442
+ };
443
+ /**
444
+ * 解析 `filename` 回傳檔名、副檔名、MIME type
445
+ *
446
+ * @param filename 檔案名稱
447
+ *
448
+ * @example
449
+ *
450
+ * parseFileInfoFromFilename('image.jpg') // ['image', 'jpg', 'image/jpeg']
451
+ * parseFileInfoFromFilename('image') // ['image', '', 'application/octet-stream']
452
+ */ const parseFileInfoFromFilename = (filename)=>{
453
+ const lastDot = filename.lastIndexOf('.');
454
+ if (lastDot === -1) return [
455
+ filename,
456
+ '',
457
+ OtherMimeType
458
+ ]; // 沒有副檔名
459
+ return [
460
+ filename.slice(0, lastDot),
461
+ filename.slice(lastDot + 1),
462
+ MimeTypeMap[filename.slice(lastDot + 1)] ?? OtherMimeType
463
+ ];
464
+ };
465
+
466
+ // const isProduction: boolean = process.env.NODE_ENV === 'production';
467
+ const prefix = 'Invariant failed';
468
+ // Throw an error if the condition fails
469
+ // Strip out error messages for production
470
+ // > Not providing an inline default argument for message as the result is smaller
471
+ function invariant(condition, // Can provide a string, or a function that returns a string for cases where
472
+ // the message takes a fair amount of effort to compute
473
+ message) {
474
+ if (condition) {
475
+ return;
476
+ }
477
+ // Condition not passed
478
+ // In production we strip the message but still throw
479
+ if (process.env.NODE_ENV === 'production') {
480
+ throw new Error(prefix);
481
+ }
482
+ // When not in production we allow the message to pass through
483
+ // *This block will be removed in production builds*
484
+ const provided = typeof message === 'function' ? message() : message;
485
+ // Options:
486
+ // 1. message provided: `${prefix}: ${provided}`
487
+ // 2. message not provided: prefix
488
+ const value = provided ? `${prefix}: ${provided}` : prefix;
489
+ throw new Error(value);
490
+ }
491
+
492
+ /**
493
+ * 判斷執行環境是否為 Server(node.js) 端
494
+ */ const isServer = ()=>typeof window === 'undefined';
495
+
496
+ /**
497
+ * 深度走訪物件/陣列,將指定鍵名且值為字串的欄位遮罩為固定字串(預設 "******")。
498
+ *
499
+ * - 預設僅遮罩鍵名等於 `password` 的欄位(區分大小寫)
500
+ * - 可傳入「字串鍵名」或「函式選擇器」來自訂要遮罩的欄位
501
+ * - 僅在值為字串時遮罩,其他型別維持原樣
502
+ * - 不變更輸入參考;回傳全新的資料結構
503
+ * - 函式選擇器會收到 `key`、`value` 與 `path`(到該鍵的路徑,陣列用索引)
504
+ * - 支援巢狀物件與陣列
505
+ *
506
+ * @param input - 來源資料
507
+ * @param mask - 遮罩字串,預設為 "******"
508
+ * @param selector - 欄位選擇器,字串或函式,預設 'password'
509
+ * @returns 回傳遮罩後的新資料
510
+ *
511
+ * @example
512
+ * const input = { password: 'secret', user: { password: 'abc' }, list: [{ password: 'x' }, { ok: 1 }] };
513
+ * const output = maskPasswords(input);
514
+ * // { password: '******', user: { password: '******' }, list: [{ password: '******' }, { ok: 1 }] }
515
+ *
516
+ * // 指定鍵名
517
+ * maskPasswords({ secret: 'xxx' }, '***', 'secret'); // { secret: '***' }
518
+ *
519
+ * // 使用函式選擇器(鍵名包含 'pass' 都遮)
520
+ * maskPasswords({ password: 'a', passcode: 'b' }, '***', (k) => k.includes('pass')); // { password: '***', passcode: '***' }
521
+ */ const maskPasswords = (input, mask = '******', selector = 'password')=>{
522
+ const isObject = (val)=>val !== null && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date);
523
+ const shouldMask = (key, value, path)=>{
524
+ if (typeof selector === 'string') {
525
+ return key === selector && typeof value === 'string';
526
+ }
527
+ return selector(key, value, path) && typeof value === 'string';
528
+ };
529
+ const walk = (val, path)=>{
530
+ if (val === null || val === undefined) return val;
531
+ if (Array.isArray(val)) {
532
+ return val.map((item, idx)=>walk(item, [
533
+ ...path,
534
+ idx
535
+ ]));
536
+ }
537
+ if (val instanceof Date) {
538
+ return new Date(val.getTime());
539
+ }
540
+ if (isObject(val)) {
541
+ const result = Object.entries(val).reduce((acc, [key, v])=>{
542
+ const nextPath = [
543
+ ...path,
544
+ key
545
+ ];
546
+ if (shouldMask(key, v, nextPath)) {
547
+ return {
548
+ ...acc,
549
+ [key]: mask
550
+ };
551
+ }
552
+ return {
553
+ ...acc,
554
+ [key]: walk(v, nextPath)
555
+ };
556
+ }, {});
557
+ return result;
558
+ }
559
+ return val;
560
+ };
561
+ return walk(input, []);
562
+ };
563
+
564
+ /**
565
+ * 將物件中的某些 key 排除
566
+ *
567
+ * @param object 原始物件
568
+ * @param keys 要排除的 key
569
+ *
570
+ * @example
571
+ * const a = { a: 1, b: 2, c: 3, d: 4 };
572
+ * const b = omit(a, 'a', 'b'); // { c: 3, d: 4 }
573
+ */ const omit = (object, ...keys)=>Object.keys(object).reduce((acc, cur)=>{
574
+ if (keys.includes(cur)) {
575
+ return acc;
576
+ }
577
+ return {
578
+ ...acc,
579
+ [cur]: object[cur]
580
+ };
581
+ }, {});
582
+ /**
583
+ * 將物件中的某些 value 排除
584
+ *
585
+ * @param object 原始物件
586
+ * @param values 要排除的 value
587
+ *
588
+ * @example
589
+ * const a = { a: undefined, b: null, c: 3, d: 4, e: [] };
590
+ * const b = omitByValue(a, undefined, null, []); // { c: 3, d: 4 }
591
+ */ const omitByValue = (object, ...values)=>Object.entries(object).reduce((acc, [key, value])=>{
592
+ // 使用深度比較檢查值是否應該被排除
593
+ const shouldOmit = values.some((excludeValue)=>isEqual(value, excludeValue));
594
+ if (shouldOmit) {
595
+ return acc;
596
+ }
597
+ return {
598
+ ...acc,
599
+ [key]: value
600
+ };
601
+ }, {});
602
+ /**
603
+ * extract the object fields by the given keys
604
+ *
605
+ * @param object - the object to pick fields from
606
+ * @param keys - the keys to pick
607
+ *
608
+ * @example
609
+ *
610
+ * const a = { a: 1, b: 2, c: 3, d: 4 };
611
+ * const b = pick(a, 'a', 'b'); // { a: 1, b: 2 }
612
+ */ const pick = (object, ...keys)=>keys.reduce((acc, cur)=>({
613
+ ...acc,
614
+ [cur]: object[cur]
615
+ }), {});
616
+ /**
617
+ * extract the object fields by the given values
618
+ *
619
+ * @param object - the object to pick fields from
620
+ * @param values - the values to pick
621
+ *
622
+ * @example
623
+ *
624
+ * const a = { a: 1, b: 2, c: 3, d: 4, e: [] };
625
+ * const b = pickByValue(a, 1, 2, []); // { a: 1, b: 2, e: [] }
626
+ */ const pickByValue = (object, ...values)=>Object.entries(object).reduce((acc, [key, value])=>{
627
+ // 使用深度比較檢查值是否應該被選擇
628
+ const shouldPick = values.some((pickValue)=>isEqual(value, pickValue));
629
+ if (shouldPick) {
630
+ return {
631
+ ...acc,
632
+ [key]: value
633
+ };
634
+ }
635
+ return acc;
636
+ }, {});
637
+ const isObject = (value)=>value !== null && typeof value === 'object';
638
+ /**
639
+ * merge two objects deeply
640
+ *
641
+ * @param target - the target object
642
+ * @param source - the source object
643
+ *
644
+ * @example
645
+ *
646
+ * const obja = { a: { a1: { a11: 'value 1', a12: 'value 2' }, a2: 'value 3' }, b: 'value 4' };
647
+ * const objb = { a: { a1: { a13: 'value 5', a14: 'value 6' }, a3: 'value 7' }};
648
+ *
649
+ * const mergeResult = deepMerge(obja, objb); // { a: { a1: { a11: 'value 1', a12: 'value 2', a13: 'value 5', a14: 'value 6' }, a2: 'value 3', a3: 'value 7' }, b: 'value 4' }
650
+ *
651
+ */ const deepMerge = (target, source)=>Object.entries(source).reduce((acc, [key, value])=>{
652
+ if (isObject(value)) {
653
+ acc[key] = key in target && isObject(target[key]) ? deepMerge(target[key], value) : value;
654
+ } else {
655
+ acc[key] = value;
656
+ }
657
+ return acc;
658
+ }, {
659
+ ...target
660
+ });
661
+ /**
662
+ * A utility function to deeply clone an object.
663
+ * @param obj - The object to clone.
664
+ *
665
+ * @example
666
+ *
667
+ * const original = { a: 1, b: { c: 2 } };
668
+ * const cloned = deepClone(original);
669
+ * console.log(cloned); // { a: 1, b: { c: 2 } }
670
+ *
671
+ */ const deepClone = (obj)=>{
672
+ if (obj === null || typeof obj !== 'object') {
673
+ return obj;
674
+ }
675
+ if (obj instanceof Date) {
676
+ return new Date(obj.getTime());
677
+ }
678
+ if (Array.isArray(obj)) {
679
+ return obj.map((item)=>deepClone(item));
680
+ }
681
+ const cloned = Object.keys(obj).reduce((acc, key)=>{
682
+ acc[key] = deepClone(obj[key]);
683
+ return acc;
684
+ }, {});
685
+ return cloned;
686
+ };
687
+ /**
688
+ * A utility function to rename a key in an object.
689
+ *
690
+ * @param obj - The object to modify.
691
+ * @param oldKey - The key to rename.
692
+ * @param newKey - The new key name.
693
+ *
694
+ * @example
695
+ *
696
+ * const obj = { a: 1, b: 2 };
697
+ * const newObj = renameKey(obj, 'a', 'c');
698
+ * console.log(newObj); // { c: 1, b: 2 }
699
+ */ const renameKey = (obj, oldKey, newKey)=>{
700
+ // 建立一個淺拷貝,避免修改原始物件
701
+ const { [oldKey]: oldValue, ...rest } = obj;
702
+ // 回傳新的物件:用新 key 存舊值,其他 key 保留
703
+ return {
704
+ ...rest,
705
+ [newKey]: oldValue
706
+ };
707
+ };
708
+
709
+ /**
710
+ * 將嵌套物件的所有屬性設為指定的布林值
711
+ * @param obj - 目標物件
712
+ * @param boolValue - 布林值
713
+ *
714
+ * @example
715
+ * const obj = { a: { b: 1, c: 2 }, d: 3 };
716
+ * const result = setBooleanToNestedObject(obj, true);
717
+ * console.log(result); // { a: { b: true, c: true }, d: true }
718
+ */ const setBooleanToNestedObject = (obj, boolValue)=>Object.entries(obj).reduce((acc, [key, value])=>{
719
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
720
+ // 如果是嵌套物件,遞歸處理
721
+ acc[key] = setBooleanToNestedObject(value, boolValue);
722
+ } else {
723
+ // 如果是基本類型,設為布林值
724
+ acc[key] = boolValue;
725
+ }
726
+ return acc;
727
+ }, {});
728
+ /**
729
+ * 深度合併配置物件的通用工具
730
+ *
731
+ * @param defaultConfig - 預設配置
732
+ * @param customConfig - 自訂配置
733
+ *
734
+ * @example
735
+ * const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
736
+ * mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
737
+ * mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
738
+ * mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
739
+ */ const mergeConfig = (defaultConfig, customConfig = {})=>{
740
+ // 深拷貝預設配置以避免修改原始物件
741
+ const result = deepClone(defaultConfig);
742
+ return Object.entries(customConfig).reduce((acc, [key, sourceValue])=>{
743
+ const targetValue = acc[key];
744
+ // 如果來源值為 null 或 undefined,跳過
745
+ if (sourceValue === null || sourceValue === undefined) {
746
+ return acc;
747
+ }
748
+ // 如果目標物件中對應的值是物件,且來源值是布林值
749
+ // 這是特殊情況:用布林值覆蓋整個嵌套物件
750
+ if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'boolean') {
751
+ // 將嵌套物件的所有屬性設為該布林值
752
+ acc[key] = setBooleanToNestedObject(targetValue, sourceValue);
753
+ } else if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
754
+ acc[key] = mergeConfig(targetValue, sourceValue);
755
+ } else {
756
+ acc[key] = sourceValue;
757
+ }
758
+ return acc;
759
+ }, result);
760
+ };
761
+
762
+ /**
763
+ * debounce function
764
+ *
765
+ * @param {Function} fn function to be executed
766
+ * @param {number} delay time to wait before executing the function
767
+ *
768
+ * @example
769
+ * const debouncedFunction = debounce((a: number, b: number) => console.log(a + b), 1000);
770
+ * debouncedFunction(1, 2);
771
+ * debouncedFunction(3, 4);
772
+ * // after 1 second
773
+ * // 7
774
+ */ const debounce = (fn, delay)=>{
775
+ let timeout;
776
+ // if (isAsync) {
777
+ // return (...args: P[]) =>
778
+ // new Promise((resolve) => {
779
+ // if (timeout) {
780
+ // clearTimeout(timeout);
781
+ // }
782
+ // timeout = setTimeout(() => {
783
+ // resolve(fn(...args));
784
+ // }, delay);
785
+ // });
786
+ // }
787
+ return (...args)=>{
788
+ if (timeout) {
789
+ clearTimeout(timeout);
790
+ }
791
+ timeout = setTimeout(()=>{
792
+ fn(...args);
793
+ }, delay);
794
+ };
795
+ };
796
+ /**
797
+ * throttle function
798
+ *
799
+ * @param {Function} fn function to be executed
800
+ * @param {number} delay time to wait before executing the function
801
+ *
802
+ * @example
803
+ * const throttledFunction = throttle((a: number, b: number) => a + b, 1000);
804
+ * throttledFunction(1, 2); // 3
805
+ * throttledFunction(3, 4); // undefined
806
+ * setTimeout(() => {
807
+ * throttledFunction(5, 6); // 11
808
+ * }, 2000);
809
+ */ const throttle = (fn, delay)=>{
810
+ let wait = false;
811
+ return (...args)=>{
812
+ if (wait) return undefined;
813
+ const val = fn(...args);
814
+ wait = true;
815
+ setTimeout(()=>{
816
+ wait = false;
817
+ }, delay);
818
+ return val;
819
+ };
820
+ };
821
+
822
+ /**
823
+ * Wait for a given amount of time
824
+ * @param ms time to wait in milliseconds
825
+ */ const wait = (ms)=>{
826
+ };
827
+
828
+ /**
829
+ * 將單層物件轉換為 URLSearchParams 物件
830
+ * - 會自動排除 null、undefined、空字串和空陣列的值
831
+ * - 陣列會以逗號連接為字串
832
+ *
833
+ * @param obj - 要轉換的物件,只支援單層物件,其中值可以是 string、number、boolean、null、undefined 或字串陣列
834
+ * @returns URLSearchParams 實例
835
+ *
836
+ * @example
837
+ * const params = { a: '1', b: 123, c: false, d: ['1','2'], e: null, f: undefined, g: '', h: [] };
838
+ * objectToSearchParams(params).toString(); // 'a=1&b=123&c=false&d=1%2C2'
839
+ */ const objectToSearchParams = (obj)=>{
840
+ const searchParams = new URLSearchParams();
841
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
842
+ return searchParams;
843
+ }
844
+ Object.entries(omitByValue(obj, undefined, null, '', [])).forEach(([key, value])=>{
845
+ // 如果是陣列,則以逗號連接
846
+ const paramValue = Array.isArray(value) ? value.join(',') : String(value);
847
+ searchParams.append(key, paramValue);
848
+ });
849
+ return searchParams;
850
+ };
851
+ /**
852
+ * 將字串值轉換為指定的型別
853
+ *
854
+ * @param value - 要轉換的字串值
855
+ * @param targetType - 目標型別
856
+ * @returns 轉換後的值
857
+ */ const convertValueByType = (value, targetType)=>{
858
+ switch(targetType){
859
+ case 'number':
860
+ return value ? Number(value) : 0;
861
+ case 'boolean':
862
+ return value === 'true' || value === '1';
863
+ case 'array':
864
+ return value ? value.split(',') : [];
865
+ case 'string':
866
+ default:
867
+ // 預設為字串,不需要轉換
868
+ return value;
869
+ }
870
+ };
871
+ /**
872
+ * 將 URLSearchParams 或字串轉換為物件
873
+ *
874
+ * @param searchParams - 要轉換的搜尋參數字串或 URLSearchParams 實例
875
+ * @param typeMap - 可選的型別轉換映射表,用於指定每個參數的目標型別
876
+ * @returns
877
+ *
878
+ * @example
879
+ * const queryString = 'a=1&b=123&c=true&d=1,2,3&e=false';
880
+ * const result = searchParamsToObject(queryString);
881
+ * // result: { a: '1', b: '123', c: 'true', d: '1,2,3', e: 'false' }
882
+ * const result = searchParamsToObject(queryString, {
883
+ * a: 'string',
884
+ * b: 'number',
885
+ * c: 'boolean',
886
+ * d: 'array',
887
+ * e: 'boolean',
888
+ * });
889
+ * // result: { a: '1', b: 123, c: true, d: ['1', '2', '3'], e: false }
890
+ */ const searchParamsToObject = (searchParams, typeMap = {})=>{
891
+ const searchParamsInstance = typeof searchParams === 'string' ? new URLSearchParams(searchParams) : searchParams;
892
+ const result = {};
893
+ // 從 URLSearchParams 取得所有參數
894
+ searchParamsInstance.forEach((value, key)=>{
895
+ if (!key) {
896
+ return;
897
+ }
898
+ const targetType = typeMap[key];
899
+ result[key] = convertValueByType(value, targetType);
900
+ });
901
+ return result;
902
+ };
15
903
 
16
904
  /**
17
905
  * convert CamelCase string to PascalCase string
@@ -502,100 +1390,6 @@ const FILE_SIZE_UNITS = [
502
1390
  * }
503
1391
  */ const isNil = (value)=>value === null || value === undefined;
504
1392
 
505
- /**
506
- * Creates a strongly-typed React Context and Provider pair for data sharing.
507
- *
508
- * This utility helps you avoid prop drilling by providing a reusable way to define
509
- * context with strict type inference. It returns a custom hook for consuming the context
510
- * and a Provider component for supplying context values.
511
- *
512
- * @template T - The value type for the context.
513
- * @returns {object} An object containing:
514
- * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
515
- * - DataProvider: A Provider component to wrap your component tree and supply the context value.
516
- *
517
- * @example
518
- * // Example usage:
519
- * const { useDataContext, DataProvider } = createDataContext<{ count: number }>();
520
- *
521
- * function Counter() {
522
- * const { count } = useDataContext();
523
- * return <span>{count}</span>;
524
- * }
525
- *
526
- * function App() {
527
- * return (
528
- * <DataProvider value={{ count: 42 }}>
529
- * <Counter />
530
- * </DataProvider>
531
- * );
532
- * }
533
- */ const createDataContext = ()=>{
534
- const Context = /*#__PURE__*/ React.createContext(undefined);
535
- const useDataContext = ()=>{
536
- const context = React.useContext(Context);
537
- if (context === undefined) {
538
- throw new Error(`useDataContext must be used within a DataProvider`);
539
- }
540
- return context;
541
- };
542
- const DataProvider = ({ children, value })=>{
543
- const memoized = React.useMemo(()=>value, [
544
- value
545
- ]);
546
- return /*#__PURE__*/ React__default.default.createElement(Context.Provider, {
547
- value: memoized
548
- }, children);
549
- };
550
- return {
551
- useDataContext,
552
- DataProvider
553
- };
554
- };
555
-
556
- /**
557
- * A hook to manage a value.
558
- *
559
- * @example
560
- *
561
- * ```tsx
562
- * const MyComponent = ({ value }: { value?: number }) => {
563
- * const [currentValue, setCurrentValue] = useValue({ value });
564
- * };
565
- * ```
566
- */ const useValue = ({ value, defaultValue })=>{
567
- if (value === undefined && defaultValue === undefined) {
568
- throw new Error('Either `value` or `defaultValue` must be provided.');
569
- }
570
- const isControlled = value !== undefined;
571
- const [internalValue, setInternalValue] = React.useState(defaultValue);
572
- const setValue = React.useCallback((newValue)=>{
573
- if (!isControlled) {
574
- setInternalValue(newValue);
575
- }
576
- }, [
577
- isControlled
578
- ]);
579
- const currentValue = isControlled ? value : internalValue;
580
- return [
581
- currentValue,
582
- setValue
583
- ];
584
- };
585
-
586
- function mergeRefs(refs) {
587
- return (value)=>{
588
- refs.forEach((ref)=>{
589
- if (typeof ref === 'function') {
590
- ref(value);
591
- } else if (ref != null) {
592
- // eslint-disable-next-line no-param-reassign
593
- ref.current = value;
594
- }
595
- });
596
- };
597
- }
598
-
599
1393
  /**
600
1394
  * 民國年轉西元年
601
1395
  * @param dateString 日期字串
@@ -677,81 +1471,72 @@ function mergeRefs(refs) {
677
1471
  return dayjs__default.default(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
678
1472
  };
679
1473
 
680
- exports.ByteSize = webStorage12s.ByteSize;
681
- exports.MimeTypeMap = webStorage12s.MimeTypeMap;
682
- exports.OtherMimeType = webStorage12s.OtherMimeType;
683
- exports.convertBytes = webStorage12s.convertBytes;
684
- exports.createEnumLikeObject = webStorage12s.createEnumLikeObject;
685
- exports.debounce = webStorage12s.debounce;
686
- exports.decodeBase64 = webStorage12s.decodeBase64;
687
- exports.decodeJson = webStorage12s.decodeJson;
688
- exports.deepClone = webStorage12s.deepClone;
689
- exports.deepMerge = webStorage12s.deepMerge;
690
- exports.encodeBase64 = webStorage12s.encodeBase64;
691
- exports.encodeJson = webStorage12s.encodeJson;
692
- exports.extractEnumLikeObject = webStorage12s.extractEnumLikeObject;
693
- exports.fakeApi = webStorage12s.fakeApi;
694
- exports.getLocalStorage = webStorage12s.getLocalStorage;
695
- exports.getMimeType = webStorage12s.getMimeType;
696
- exports.invariant = webStorage12s.invariant;
697
- exports.isEqual = webStorage12s.isEqual;
698
- exports.isServer = webStorage12s.isServer;
699
- exports.mergeConfig = webStorage12s.mergeConfig;
700
- exports.objectToSearchParams = webStorage12s.objectToSearchParams;
701
- exports.omit = webStorage12s.omit;
702
- exports.omitByValue = webStorage12s.omitByValue;
703
- exports.parseFileInfoFromFilename = webStorage12s.parseFileInfoFromFilename;
704
- exports.parseFilenameFromDisposition = webStorage12s.parseFilenameFromDisposition;
705
- exports.pick = webStorage12s.pick;
706
- exports.pickByValue = webStorage12s.pickByValue;
707
- exports.renameKey = webStorage12s.renameKey;
708
- exports.searchParamsToObject = webStorage12s.searchParamsToObject;
709
- exports.setLocalStorage = webStorage12s.setLocalStorage;
710
- exports.throttle = webStorage12s.throttle;
711
- exports.validateFileType = webStorage12s.validateFileType;
712
- exports.wait = webStorage12s.wait;
713
- exports.QueryProvider = queryStore12s.QueryProvider;
714
- exports.useQueryContext = queryStore12s.useQueryContext;
715
- exports.useCountdown = useCountdown12s.useCountdown;
716
- exports.useDisclosure = useDisclosure12s.useDisclosure;
717
- exports.downloadFile = download12s.downloadFile;
1474
+ exports.ByteSize = ByteSize;
1475
+ exports.MimeTypeMap = MimeTypeMap;
1476
+ exports.OtherMimeType = OtherMimeType;
718
1477
  exports.adToRocEra = adToRocEra;
719
1478
  exports.camelCase2PascalCase = camelCase2PascalCase;
720
1479
  exports.camelCase2SnakeCase = camelCase2SnakeCase;
721
1480
  exports.camelString2PascalString = camelString2PascalString;
722
1481
  exports.camelString2SnakeString = camelString2SnakeString;
723
- exports.createDataContext = createDataContext;
1482
+ exports.convertBytes = convertBytes;
1483
+ exports.createEnumLikeObject = createEnumLikeObject;
1484
+ exports.debounce = debounce;
1485
+ exports.decodeBase64 = decodeBase64;
1486
+ exports.decodeJson = decodeJson;
1487
+ exports.deepClone = deepClone;
1488
+ exports.deepMerge = deepMerge;
1489
+ exports.encodeBase64 = encodeBase64;
1490
+ exports.encodeJson = encodeJson;
1491
+ exports.extractEnumLikeObject = extractEnumLikeObject;
1492
+ exports.fakeApi = fakeApi;
724
1493
  exports.formatAmount = formatAmount;
725
1494
  exports.formatBytes = formatBytes;
726
1495
  exports.formatStarMask = formatStarMask;
727
1496
  exports.generatePeriodArray = generatePeriodArray;
728
1497
  exports.getCurrentPeriod = getCurrentPeriod;
1498
+ exports.getMimeType = getMimeType;
1499
+ exports.invariant = invariant;
729
1500
  exports.isChinese = isChinese;
730
1501
  exports.isDateString = isDateString;
731
1502
  exports.isDateTimeString = isDateTimeString;
732
1503
  exports.isEmail = isEmail;
733
1504
  exports.isEnglish = isEnglish;
1505
+ exports.isEqual = isEqual;
734
1506
  exports.isNil = isNil;
735
1507
  exports.isNonZeroStart = isNonZeroStart;
736
1508
  exports.isNumber = isNumber;
737
1509
  exports.isNumberAtLeastN = isNumberAtLeastN;
738
1510
  exports.isNumberN = isNumberN;
739
1511
  exports.isNumberNM = isNumberNM;
1512
+ exports.isServer = isServer;
740
1513
  exports.isTWMobile = isTWMobile;
741
1514
  exports.isTWPhone = isTWPhone;
742
1515
  exports.isTimeString = isTimeString;
743
1516
  exports.isValidPassword = isValidPassword;
1517
+ exports.maskPasswords = maskPasswords;
744
1518
  exports.maskString = maskString;
745
- exports.mergeRefs = mergeRefs;
1519
+ exports.mergeConfig = mergeConfig;
1520
+ exports.objectToSearchParams = objectToSearchParams;
1521
+ exports.omit = omit;
1522
+ exports.omitByValue = omitByValue;
1523
+ exports.parseFileInfoFromFilename = parseFileInfoFromFilename;
1524
+ exports.parseFilenameFromDisposition = parseFilenameFromDisposition;
746
1525
  exports.pascalCase2CamelCase = pascalCase2CamelCase;
747
1526
  exports.pascalCase2SnakeCase = pascalCase2SnakeCase;
748
1527
  exports.pascalString2CamelString = pascalString2CamelString;
749
1528
  exports.pascalString2SnakeString = pascalString2SnakeString;
1529
+ exports.pick = pick;
1530
+ exports.pickByValue = pickByValue;
1531
+ exports.renameKey = renameKey;
750
1532
  exports.rocEraToAd = rocEraToAd;
1533
+ exports.searchParamsToObject = searchParamsToObject;
751
1534
  exports.snakeCase2CamelCase = snakeCase2CamelCase;
752
1535
  exports.snakeCase2PascalCase = snakeCase2PascalCase;
753
1536
  exports.snakeString2CamelString = snakeString2CamelString;
754
1537
  exports.snakeString2PascalString = snakeString2PascalString;
755
- exports.useValue = useValue;
1538
+ exports.throttle = throttle;
756
1539
  exports.validTaxId = validTaxId;
757
1540
  exports.validateDateString = validateDateString;
1541
+ exports.validateFileType = validateFileType;
1542
+ exports.wait = wait;