@gateweb/react-utils 1.12.2 → 1.14.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
@@ -5,7 +5,7 @@ var queryStoreClient = require('./queryStore-client-q_SLGgYH.js');
5
5
  var React = require('react');
6
6
  var useCountdownClient = require('./useCountdown-client-uiqhgllY.js');
7
7
  var downloadClient = require('./download-client-DKxkL92w.js');
8
- var webStorageClient = require('./webStorage-client-BGQKUfrO.js');
8
+ var webStorageClient = require('./webStorage-client-DHr9PcPl.js');
9
9
 
10
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
@@ -238,11 +238,31 @@ function createEnumLikeObject(obj, name, scene) {
238
238
  }, time);
239
239
  });
240
240
 
241
+ const MimeTypeMap = {
242
+ jpeg: 'image/jpeg',
243
+ jpg: 'image/jpeg',
244
+ gif: 'image/gif',
245
+ png: 'image/png',
246
+ pdf: 'application/pdf',
247
+ zip: 'application/zip',
248
+ csv: 'text/csv',
249
+ ppt: 'application/vnd.ms-powerpoint',
250
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
251
+ xls: 'application/vnd.ms-excel',
252
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
253
+ doc: 'application/msword',
254
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
255
+ txt: 'text/plain'
256
+ };
257
+ const OtherMimeType = 'application/octet-stream';
258
+
241
259
  /**
242
- * 檢查檔案是否為合法的 MIME 類型
260
+ * 檢查檔案是否為合法的檔案類型
261
+ *
262
+ * `accepts` 可同時接受副檔名以及 MIME 類型
243
263
  *
244
264
  * @param file 檔案
245
- * @param accepts 允許的 MIME 類型
265
+ * @param accepts 允許的類型
246
266
  *
247
267
  * @example
248
268
  *
@@ -250,21 +270,24 @@ function createEnumLikeObject(obj, name, scene) {
250
270
  * validateFileType({ type: 'image/png' }, ['image/png', 'image/jpeg']) // true
251
271
  * validateFileType({ type: 'image/png' }, ['image/jpeg']) // false
252
272
  * validateFileType({ type: 'image/png' }, ['image/*']) // true
273
+ * validateFileType({ name: '圖片.png', type: 'image/png' }, ['.png']) // true
253
274
  * ```
254
275
  */ const validateFileType = (file, accepts)=>{
255
276
  if (accepts.length === 0) return true;
256
277
  // 獲取文件的MIME類型
257
278
  const fileMimeType = file.type;
279
+ // 提取副檔名(含 .,且轉小寫)
280
+ const fileExt = file.name.includes('.') ? `.${file.name.split('.').pop().toLowerCase()}` : '';
258
281
  return accepts.some((accept)=>{
282
+ if (accept.startsWith('.')) {
283
+ // 以副檔名檢查,忽略大小寫
284
+ return fileExt === accept.toLowerCase();
285
+ }
259
286
  if (accept === fileMimeType) {
260
287
  return true;
261
288
  }
262
289
  if (accept.endsWith('/*')) {
263
- const acceptedCategory = accept.split('/')[0];
264
- const fileCategory = fileMimeType.split('/')[0];
265
- if (acceptedCategory === fileCategory) {
266
- return true;
267
- }
290
+ return accept.split('/')[0] === fileMimeType.split('/')[0];
268
291
  }
269
292
  return false;
270
293
  });
@@ -291,32 +314,70 @@ function createEnumLikeObject(obj, name, scene) {
291
314
  * getMimeType('xlsx') // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
292
315
  * getMimeType('doc') // 'application/msword'
293
316
  * getMimeType('docx') // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
317
+ *
318
+ * @deprecated use `parseFileInfoFromFilename` instead
294
319
  */ const getMimeType = (fileName)=>{
295
- switch((fileName.split('.').pop() || '').toLocaleLowerCase()){
296
- case 'jpeg':
297
- case 'jpg':
298
- return 'image/jpeg';
299
- case 'png':
300
- return 'image/png';
301
- case 'pdf':
302
- return 'application/pdf';
303
- case 'zip':
304
- return 'application/zip';
305
- case 'csv':
306
- return 'text/csv';
307
- case 'xls':
308
- return 'application/vnd.ms-excel';
309
- case 'xlsx':
310
- return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
311
- case 'doc':
312
- return 'application/msword';
313
- case 'docx':
314
- return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
315
- case 'txt':
316
- return 'text/plain';
317
- default:
318
- return 'application/octet-stream';
320
+ const ext = (fileName.split('.').pop() || '').toLowerCase();
321
+ return MimeTypeMap[ext] ?? OtherMimeType;
322
+ };
323
+ /**
324
+ * 用來解析後端在 response header content-disposition 的內容
325
+ *
326
+ * 一般來說格式會有以下兩種
327
+ *
328
+ * - Content-Disposition: attachment; filename="file name.jpg"
329
+ * - Content-Disposition: attachment; filename*=UTF-8''file%20name2.jpg
330
+ *
331
+ * 如果格式正確就會取得檔案名稱(包含副檔名),優先取 filename* 內的內容
332
+ *
333
+ * @param disposition Content-Disposition
334
+ *
335
+ * @example
336
+ *
337
+ * parseFilenameFromDisposition('attachment; filename="file name1.jpg') // file name.jpg
338
+ * parseFilenameFromDisposition('attachment; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
339
+ * parseFilenameFromDisposition('attachment; filename="file name.jpg; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
340
+ */ const parseFilenameFromDisposition = (disposition)=>{
341
+ // 1. 先找 filename*
342
+ const filenameStarMatch = disposition.match(/filename\*\s*=\s*(?:UTF-8'')?([^;]+)/i);
343
+ if (filenameStarMatch && filenameStarMatch[1]) {
344
+ // 依 RFC 5987 格式(UTF-8''URL-ENCODED),要先 decode
345
+ try {
346
+ return decodeURIComponent(filenameStarMatch[1].replace(/(^['"]|['"]$)/g, ''));
347
+ } catch {
348
+ // fallback,如果 decode 失敗,直接傳回原值
349
+ return filenameStarMatch[1];
350
+ }
319
351
  }
352
+ // 2. 沒有 filename*,再找 filename
353
+ const filenameMatch = disposition.match(/filename\s*=\s*("?)([^";]+)\1/i);
354
+ if (filenameMatch && filenameMatch[2]) {
355
+ return filenameMatch[2];
356
+ }
357
+ // 3. 都沒有則 undefined
358
+ return undefined;
359
+ };
360
+ /**
361
+ * 解析 `filename` 回傳檔名、副檔名、MIME type
362
+ *
363
+ * @param filename 檔案名稱
364
+ *
365
+ * @example
366
+ *
367
+ * parseFileInfoFromFilename('image.jpg') // ['image', 'jpg', 'image/jpeg']
368
+ * parseFileInfoFromFilename('image') // ['image', '', 'application/octet-stream']
369
+ */ const parseFileInfoFromFilename = (filename)=>{
370
+ const lastDot = filename.lastIndexOf('.');
371
+ if (lastDot === -1) return [
372
+ filename,
373
+ '',
374
+ OtherMimeType
375
+ ]; // 沒有副檔名
376
+ return [
377
+ filename.slice(0, lastDot),
378
+ filename.slice(lastDot + 1),
379
+ MimeTypeMap[filename.slice(lastDot + 1)] ?? OtherMimeType
380
+ ];
320
381
  };
321
382
 
322
383
  // const isProduction: boolean = process.env.NODE_ENV === 'production';
@@ -446,6 +507,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
446
507
  }, {
447
508
  ...target
448
509
  });
510
+ /**
511
+ * A utility function to deeply clone an object.
512
+ * @param obj - The object to clone.
513
+ *
514
+ * @example
515
+ *
516
+ * const original = { a: 1, b: { c: 2 } };
517
+ * const cloned = deepClone(original);
518
+ * console.log(cloned); // { a: 1, b: { c: 2 } }
519
+ *
520
+ */ const deepClone = (obj)=>{
521
+ if (obj === null || typeof obj !== 'object') {
522
+ return obj;
523
+ }
524
+ if (obj instanceof Date) {
525
+ return new Date(obj.getTime());
526
+ }
527
+ if (Array.isArray(obj)) {
528
+ return obj.map((item)=>deepClone(item));
529
+ }
530
+ const cloned = Object.keys(obj).reduce((acc, key)=>{
531
+ acc[key] = deepClone(obj[key]);
532
+ return acc;
533
+ }, {});
534
+ return cloned;
535
+ };
536
+
537
+ /**
538
+ * 將嵌套物件的所有屬性設為指定的布林值
539
+ * @param obj - 目標物件
540
+ * @param boolValue - 布林值
541
+ *
542
+ * @example
543
+ * const obj = { a: { b: 1, c: 2 }, d: 3 };
544
+ * const result = setBooleanToNestedObject(obj, true);
545
+ * console.log(result); // { a: { b: true, c: true }, d: true }
546
+ */ const setBooleanToNestedObject = (obj, boolValue)=>Object.entries(obj).reduce((acc, [key, value])=>{
547
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
548
+ // 如果是嵌套物件,遞歸處理
549
+ acc[key] = setBooleanToNestedObject(value, boolValue);
550
+ } else {
551
+ // 如果是基本類型,設為布林值
552
+ acc[key] = boolValue;
553
+ }
554
+ return acc;
555
+ }, {});
556
+ /**
557
+ * 深度合併配置物件的通用工具
558
+ *
559
+ * @param defaultConfig - 預設配置
560
+ * @param customConfig - 自訂配置
561
+ *
562
+ * @example
563
+ * const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
564
+ * mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
565
+ * mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
566
+ * mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
567
+ */ const mergeConfig = (defaultConfig, customConfig = {})=>{
568
+ // 深拷貝預設配置以避免修改原始物件
569
+ const result = deepClone(defaultConfig);
570
+ return Object.entries(customConfig).reduce((acc, [key, sourceValue])=>{
571
+ const targetValue = acc[key];
572
+ // 如果來源值為 null 或 undefined,跳過
573
+ if (sourceValue === null || sourceValue === undefined) {
574
+ return acc;
575
+ }
576
+ // 如果目標物件中對應的值是物件,且來源值是布林值
577
+ // 這是特殊情況:用布林值覆蓋整個嵌套物件
578
+ if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'boolean') {
579
+ // 將嵌套物件的所有屬性設為該布林值
580
+ acc[key] = setBooleanToNestedObject(targetValue, sourceValue);
581
+ } else if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
582
+ acc[key] = mergeConfig(targetValue, sourceValue);
583
+ } else {
584
+ acc[key] = sourceValue;
585
+ }
586
+ return acc;
587
+ }, result);
588
+ };
449
589
 
450
590
  /**
451
591
  * debounce function
@@ -855,8 +995,7 @@ const FILE_SIZE_UNITS = [
855
995
  *
856
996
  * isEmail().test('123') // false
857
997
  * isEmail().test('123@gmail.com') // true
858
- */ const isEmail = ()=>// eslint-disable-next-line
859
- /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
998
+ */ const isEmail = ()=>/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\\-]*\.)+[A-Z]{2,}$/i;
860
999
  /**
861
1000
  * 日期字串
862
1001
  *
@@ -1055,6 +1194,29 @@ const FILE_SIZE_UNITS = [
1055
1194
  // 再用 dayjs 驗證是否為有效日期
1056
1195
  return dayjs__default.default(dateString, format, true).isValid();
1057
1196
  };
1197
+ /**
1198
+ * 判斷 `value` 是否為 `null` 或 `undefined`
1199
+ *
1200
+ * @param value 值
1201
+ *
1202
+ * @example
1203
+ * isNil(null); // true
1204
+ * isNil(undefined); // true
1205
+ * isNil(0); // false
1206
+ * isNil(''); // false
1207
+ * isNil(false); // false
1208
+ * isNil({}); // false
1209
+ *
1210
+ * // TypeScript 型別縮窄應用
1211
+ * function example(input?: string | null) {
1212
+ * if (isNil(input)) {
1213
+ * // input 型別會自動縮窄成 null | undefined
1214
+ * return 'No value';
1215
+ * }
1216
+ * // input 型別自動為 string
1217
+ * return input.toUpperCase();
1218
+ * }
1219
+ */ const isNil = (value)=>value === null || value === undefined;
1058
1220
 
1059
1221
  /**
1060
1222
  * Creates a strongly-typed React Context and Provider pair for data sharing.
@@ -1064,7 +1226,7 @@ const FILE_SIZE_UNITS = [
1064
1226
  * and a Provider component for supplying context values.
1065
1227
  *
1066
1228
  * @template T - The value type for the context.
1067
- * @returns {Object} An object containing:
1229
+ * @returns {object} An object containing:
1068
1230
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1069
1231
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1070
1232
  *
@@ -1238,6 +1400,8 @@ exports.downloadFile = downloadClient.downloadFile;
1238
1400
  exports.getLocalStorage = webStorageClient.getLocalStorage;
1239
1401
  exports.setLocalStorage = webStorageClient.setLocalStorage;
1240
1402
  exports.ByteSize = ByteSize;
1403
+ exports.MimeTypeMap = MimeTypeMap;
1404
+ exports.OtherMimeType = OtherMimeType;
1241
1405
  exports.adToRocEra = adToRocEra;
1242
1406
  exports.camelCase2PascalCase = camelCase2PascalCase;
1243
1407
  exports.camelCase2SnakeCase = camelCase2SnakeCase;
@@ -1247,6 +1411,7 @@ exports.convertBytes = convertBytes;
1247
1411
  exports.createDataContext = createDataContext;
1248
1412
  exports.createEnumLikeObject = createEnumLikeObject;
1249
1413
  exports.debounce = debounce;
1414
+ exports.deepClone = deepClone;
1250
1415
  exports.deepMerge = deepMerge;
1251
1416
  exports.extractEnumLikeObject = extractEnumLikeObject;
1252
1417
  exports.fakeApi = fakeApi;
@@ -1263,6 +1428,7 @@ exports.isDateTimeString = isDateTimeString;
1263
1428
  exports.isEmail = isEmail;
1264
1429
  exports.isEnglish = isEnglish;
1265
1430
  exports.isEqual = isEqual;
1431
+ exports.isNil = isNil;
1266
1432
  exports.isNonZeroStart = isNonZeroStart;
1267
1433
  exports.isNumber = isNumber;
1268
1434
  exports.isNumberAtLeastN = isNumberAtLeastN;
@@ -1274,10 +1440,13 @@ exports.isTWPhone = isTWPhone;
1274
1440
  exports.isTimeString = isTimeString;
1275
1441
  exports.isValidPassword = isValidPassword;
1276
1442
  exports.maskString = maskString;
1443
+ exports.mergeConfig = mergeConfig;
1277
1444
  exports.mergeRefs = mergeRefs;
1278
1445
  exports.objectToSearchParams = objectToSearchParams;
1279
1446
  exports.omit = omit;
1280
1447
  exports.omitByValue = omitByValue;
1448
+ exports.parseFileInfoFromFilename = parseFileInfoFromFilename;
1449
+ exports.parseFilenameFromDisposition = parseFilenameFromDisposition;
1281
1450
  exports.pascalCase2CamelCase = pascalCase2CamelCase;
1282
1451
  exports.pascalCase2SnakeCase = pascalCase2SnakeCase;
1283
1452
  exports.pascalString2CamelString = pascalString2CamelString;
@@ -187,5 +187,18 @@ type TChangeKeyType<T, K extends keyof T, V> = Omit<T, K> & {
187
187
  type MapToType<T extends Record<any, any>, A = any> = {
188
188
  [K in keyof T]: A;
189
189
  };
190
+ /**
191
+ * 覆寫 T 指定屬性的型別,保留其他屬性。
192
+ *
193
+ * @template T 原始型別
194
+ * @template R 欲覆寫的 key-value 結構
195
+ *
196
+ * @example
197
+ * type User = { id: number; name: string }
198
+ * type FormUser = OverrideProps<User, { id: string }> // { id: string; name: string }
199
+ */
200
+ type OverrideProps<T, R extends {
201
+ [K in keyof R]: any;
202
+ }> = Omit<T, keyof R> & R;
190
203
 
191
- export type { AtLeastOne, DeepPartial, Entries, MapToString, MapToType, OnlyOne, PopArgs, PushArgs, ShiftArgs, TChangeKeyType, TExtractValueType, UnshiftArgs };
204
+ export type { AtLeastOne, DeepPartial, Entries, MapToString, MapToType, OnlyOne, OverrideProps, PopArgs, PushArgs, ShiftArgs, TChangeKeyType, TExtractValueType, UnshiftArgs };
@@ -20,7 +20,7 @@
20
20
  } else {
21
21
  currentObject = JSON.parse(storage);
22
22
  }
23
- } catch (error) {
23
+ } catch (_error) {
24
24
  return undefined;
25
25
  }
26
26
  // let currentObject = JSON.parse(storage);
@@ -404,7 +404,9 @@ declare const extractEnumLikeObject: <T extends Record<string, { [P in K]: any;
404
404
  *
405
405
  * @param obj 要處理的物件
406
406
  * @param name 生成的 enum 名稱
407
- * @param scene 要使用的條件 (參考 example)
407
+ *
408
+ * @remarks
409
+ * 若需多場景 label,請傳入第三參數 scene,詳細用法請見範例。
408
410
  *
409
411
  * @example
410
412
  *
@@ -419,7 +421,7 @@ declare const extractEnumLikeObject: <T extends Record<string, { [P in K]: any;
419
421
  *
420
422
  * // scenes 版本
421
423
  *
422
- * Status = { Enabled: { value: 'enabled', scenes: { webA: { label: '啟用' }, webB: { label: '激活' } } }, Disabled: { value: 'disabled', scenes: { webA: { label: '停用' }, webB: { label: '停止' }}}}
424
+ * const Status = { Enabled: { value: 'enabled', scenes: { webA: { label: '啟用' }, webB: { label: '激活' } } }, Disabled: { value: 'disabled', scenes: { webA: { label: '停用' }, webB: { label: '停止' }}}}
423
425
  * const { EnumStatusA, StatusAList, getStatusALabel } = createEnumLikeObject(Status, 'StatusA', 'webA');
424
426
  * console.log(EnumStatusA); // { Enabled: 'enabled', Disabled: 'disabled' }
425
427
  * console.log(StatusAList); // [ { key: 'Enabled', value: 'enabled', label: '啟用' }, { key: 'Disabled', value: 'disabled', label: '停用' }]
@@ -431,6 +433,7 @@ declare const extractEnumLikeObject: <T extends Record<string, { [P in K]: any;
431
433
  * console.log(getStatusBLabel('enabled')); // '激活'
432
434
  *
433
435
  * ```
436
+ *
434
437
  */
435
438
  declare function createEnumLikeObject<T extends Record<string, {
436
439
  value: any;
@@ -474,11 +477,33 @@ declare const fakeApi: <T>(returnValue: T, result?: boolean, time?: number) => P
474
477
  message: string;
475
478
  }>;
476
479
 
480
+ declare const MimeTypeMap: {
481
+ readonly jpeg: "image/jpeg";
482
+ readonly jpg: "image/jpeg";
483
+ readonly gif: "image/gif";
484
+ readonly png: "image/png";
485
+ readonly pdf: "application/pdf";
486
+ readonly zip: "application/zip";
487
+ readonly csv: "text/csv";
488
+ readonly ppt: "application/vnd.ms-powerpoint";
489
+ readonly pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation";
490
+ readonly xls: "application/vnd.ms-excel";
491
+ readonly xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
492
+ readonly doc: "application/msword";
493
+ readonly docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
494
+ readonly txt: "text/plain";
495
+ };
496
+ declare const OtherMimeType = "application/octet-stream";
497
+ type MimeTypeExtension = keyof typeof MimeTypeMap;
498
+ type MimeTypeValue = (typeof MimeTypeMap)[MimeTypeExtension] | typeof OtherMimeType;
499
+
477
500
  /**
478
- * 檢查檔案是否為合法的 MIME 類型
501
+ * 檢查檔案是否為合法的檔案類型
502
+ *
503
+ * `accepts` 可同時接受副檔名以及 MIME 類型
479
504
  *
480
505
  * @param file 檔案
481
- * @param accepts 允許的 MIME 類型
506
+ * @param accepts 允許的類型
482
507
  *
483
508
  * @example
484
509
  *
@@ -486,6 +511,7 @@ declare const fakeApi: <T>(returnValue: T, result?: boolean, time?: number) => P
486
511
  * validateFileType({ type: 'image/png' }, ['image/png', 'image/jpeg']) // true
487
512
  * validateFileType({ type: 'image/png' }, ['image/jpeg']) // false
488
513
  * validateFileType({ type: 'image/png' }, ['image/*']) // true
514
+ * validateFileType({ name: '圖片.png', type: 'image/png' }, ['.png']) // true
489
515
  * ```
490
516
  */
491
517
  declare const validateFileType: (file: File, accepts: string[]) => boolean;
@@ -511,8 +537,40 @@ declare const validateFileType: (file: File, accepts: string[]) => boolean;
511
537
  * getMimeType('xlsx') // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
512
538
  * getMimeType('doc') // 'application/msword'
513
539
  * getMimeType('docx') // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
540
+ *
541
+ * @deprecated use `parseFileInfoFromFilename` instead
542
+ */
543
+ declare const getMimeType: (fileName: string) => "image/jpeg" | "image/gif" | "image/png" | "application/pdf" | "application/zip" | "text/csv" | "application/vnd.ms-powerpoint" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "text/plain";
544
+ /**
545
+ * 用來解析後端在 response header content-disposition 的內容
546
+ *
547
+ * 一般來說格式會有以下兩種
548
+ *
549
+ * - Content-Disposition: attachment; filename="file name.jpg"
550
+ * - Content-Disposition: attachment; filename*=UTF-8''file%20name2.jpg
551
+ *
552
+ * 如果格式正確就會取得檔案名稱(包含副檔名),優先取 filename* 內的內容
553
+ *
554
+ * @param disposition Content-Disposition
555
+ *
556
+ * @example
557
+ *
558
+ * parseFilenameFromDisposition('attachment; filename="file name1.jpg') // file name.jpg
559
+ * parseFilenameFromDisposition('attachment; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
560
+ * parseFilenameFromDisposition('attachment; filename="file name.jpg; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
561
+ */
562
+ declare const parseFilenameFromDisposition: (disposition: string) => string | undefined;
563
+ /**
564
+ * 解析 `filename` 回傳檔名、副檔名、MIME type
565
+ *
566
+ * @param filename 檔案名稱
567
+ *
568
+ * @example
569
+ *
570
+ * parseFileInfoFromFilename('image.jpg') // ['image', 'jpg', 'image/jpeg']
571
+ * parseFileInfoFromFilename('image') // ['image', '', 'application/octet-stream']
514
572
  */
515
- declare const getMimeType: (fileName: string) => "image/jpeg" | "image/png" | "application/pdf" | "application/zip" | "text/csv" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "text/plain" | "application/octet-stream";
573
+ declare const parseFileInfoFromFilename: (filename: string) => [string, string, MimeTypeValue];
516
574
 
517
575
  declare function invariant(condition: any, message?: string | (() => string)): asserts condition;
518
576
 
@@ -521,6 +579,23 @@ declare function invariant(condition: any, message?: string | (() => string)): a
521
579
  */
522
580
  declare const isServer: () => boolean;
523
581
 
582
+ type DeepPartialWithBooleanOverride<T> = {
583
+ [K in keyof T]?: T[K] extends object ? T[K] extends boolean ? T[K] : DeepPartialWithBooleanOverride<T[K]> | boolean : T[K];
584
+ };
585
+ /**
586
+ * 深度合併配置物件的通用工具
587
+ *
588
+ * @param defaultConfig - 預設配置
589
+ * @param customConfig - 自訂配置
590
+ *
591
+ * @example
592
+ * const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
593
+ * mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
594
+ * mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
595
+ * mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
596
+ */
597
+ declare const mergeConfig: <T extends Record<string, any>>(defaultConfig: T, customConfig?: DeepPartialWithBooleanOverride<T>) => T;
598
+
524
599
  /**
525
600
  * 將物件中的某些 key 排除
526
601
  *
@@ -616,6 +691,18 @@ type RequiredBy<T, K extends keyof T, RemoveUndefined extends boolean = false> =
616
691
  type PartialBy<T, K extends keyof T, RemoveUndefined extends boolean = false> = Omit<T, K> & {
617
692
  [P in K]?: RemoveUndefined extends true ? Exclude<T[P], undefined> : T[P];
618
693
  };
694
+ /**
695
+ * A utility function to deeply clone an object.
696
+ * @param obj - The object to clone.
697
+ *
698
+ * @example
699
+ *
700
+ * const original = { a: 1, b: { c: 2 } };
701
+ * const cloned = deepClone(original);
702
+ * console.log(cloned); // { a: 1, b: { c: 2 } }
703
+ *
704
+ */
705
+ declare const deepClone: <T>(obj: T) => T;
619
706
 
620
707
  /**
621
708
  * debounce function
@@ -928,6 +1015,30 @@ declare const validTaxId: (taxId: string) => boolean;
928
1015
  * validateDateString('20210201', 'YYYYMM') // false
929
1016
  */
930
1017
  declare const validateDateString: (dateString: string, format: string) => boolean;
1018
+ /**
1019
+ * 判斷 `value` 是否為 `null` 或 `undefined`
1020
+ *
1021
+ * @param value 值
1022
+ *
1023
+ * @example
1024
+ * isNil(null); // true
1025
+ * isNil(undefined); // true
1026
+ * isNil(0); // false
1027
+ * isNil(''); // false
1028
+ * isNil(false); // false
1029
+ * isNil({}); // false
1030
+ *
1031
+ * // TypeScript 型別縮窄應用
1032
+ * function example(input?: string | null) {
1033
+ * if (isNil(input)) {
1034
+ * // input 型別會自動縮窄成 null | undefined
1035
+ * return 'No value';
1036
+ * }
1037
+ * // input 型別自動為 string
1038
+ * return input.toUpperCase();
1039
+ * }
1040
+ */
1041
+ declare const isNil: (value: unknown) => value is null | undefined;
931
1042
 
932
1043
  type TQueryProps<Q> = {
933
1044
  /**
@@ -987,7 +1098,7 @@ declare const useQueryContext: <Q>() => <T>(selector: (state: TQueryState<Q>) =>
987
1098
  * and a Provider component for supplying context values.
988
1099
  *
989
1100
  * @template T - The value type for the context.
990
- * @returns {Object} An object containing:
1101
+ * @returns {object} An object containing:
991
1102
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
992
1103
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
993
1104
  *
@@ -1149,4 +1260,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
1149
1260
  */
1150
1261
  declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
1151
1262
 
1152
- export { ByteSize, type PartialBy, QueryProvider, type RequiredBy, type TCountdownActions, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createDataContext, createEnumLikeObject, debounce, deepMerge, downloadFile, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, getLocalStorage, getMimeType, invariant, isChinese, isDateString, isDateTimeString, isEmail, isEnglish, isEqual, isNonZeroStart, isNumber, isNumberAtLeastN, isNumberN, isNumberNM, isServer, isTWMobile, isTWPhone, isTimeString, isValidPassword, maskString, mergeRefs, objectToSearchParams, omit, omitByValue, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, rocEraToAd, searchParamsToObject, setLocalStorage, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useCountdown, useQueryContext, useValue, validTaxId, validateDateString, validateFileType, wait };
1263
+ export { ByteSize, MimeTypeMap, OtherMimeType, QueryProvider, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createDataContext, createEnumLikeObject, debounce, deepClone, deepMerge, downloadFile, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, getLocalStorage, getMimeType, invariant, isChinese, isDateString, isDateTimeString, isEmail, isEnglish, isEqual, isNil, isNonZeroStart, isNumber, isNumberAtLeastN, isNumberN, isNumberNM, isServer, isTWMobile, isTWPhone, isTimeString, isValidPassword, maskString, mergeConfig, mergeRefs, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, rocEraToAd, searchParamsToObject, setLocalStorage, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useCountdown, useQueryContext, useValue, validTaxId, validateDateString, validateFileType, wait };
1264
+ export type { MimeTypeExtension, MimeTypeValue, PartialBy, RequiredBy, TCountdownActions };