@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/README.md +134 -46
- package/dist/cjs/index.d.ts +119 -7
- package/dist/cjs/index.js +204 -35
- package/dist/cjs/types.d.ts +14 -1
- package/dist/cjs/{webStorage-client-BGQKUfrO.js → webStorage-client-DHr9PcPl.js} +1 -1
- package/dist/es/index.d.mts +119 -7
- package/dist/es/index.mjs +200 -38
- package/dist/es/{queryStore-client-vG-bXFYm.mjs → queryStore-client-CFQTVwrg.mjs} +1 -1
- package/dist/es/types.d.mts +14 -1
- package/dist/es/{webStorage-client-Pd-loNCg.mjs → webStorage-client-W1DItzhS.mjs} +1 -1
- package/package.json +27 -22
package/dist/es/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
|
-
export { Q as QueryProvider, u as useQueryContext } from './queryStore-client-
|
|
3
|
-
import React, {
|
|
2
|
+
export { Q as QueryProvider, u as useQueryContext } from './queryStore-client-CFQTVwrg.mjs';
|
|
3
|
+
import React, { useMemo, createContext, useContext, useState, useCallback } from 'react';
|
|
4
4
|
export { u as useCountdown } from './useCountdown-client-t52WIHfq.mjs';
|
|
5
5
|
export { d as downloadFile } from './download-client-CnaJ0p_f.mjs';
|
|
6
|
-
export { g as getLocalStorage, s as setLocalStorage } from './webStorage-client-
|
|
6
|
+
export { g as getLocalStorage, s as setLocalStorage } from './webStorage-client-W1DItzhS.mjs';
|
|
7
7
|
|
|
8
8
|
const FILE_SIZE_UNITS$1 = [
|
|
9
9
|
'Bytes',
|
|
@@ -231,11 +231,31 @@ function createEnumLikeObject(obj, name, scene) {
|
|
|
231
231
|
}, time);
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
+
const MimeTypeMap = {
|
|
235
|
+
jpeg: 'image/jpeg',
|
|
236
|
+
jpg: 'image/jpeg',
|
|
237
|
+
gif: 'image/gif',
|
|
238
|
+
png: 'image/png',
|
|
239
|
+
pdf: 'application/pdf',
|
|
240
|
+
zip: 'application/zip',
|
|
241
|
+
csv: 'text/csv',
|
|
242
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
243
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
244
|
+
xls: 'application/vnd.ms-excel',
|
|
245
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
246
|
+
doc: 'application/msword',
|
|
247
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
248
|
+
txt: 'text/plain'
|
|
249
|
+
};
|
|
250
|
+
const OtherMimeType = 'application/octet-stream';
|
|
251
|
+
|
|
234
252
|
/**
|
|
235
|
-
*
|
|
253
|
+
* 檢查檔案是否為合法的檔案類型
|
|
254
|
+
*
|
|
255
|
+
* `accepts` 可同時接受副檔名以及 MIME 類型
|
|
236
256
|
*
|
|
237
257
|
* @param file 檔案
|
|
238
|
-
* @param accepts
|
|
258
|
+
* @param accepts 允許的類型
|
|
239
259
|
*
|
|
240
260
|
* @example
|
|
241
261
|
*
|
|
@@ -243,21 +263,24 @@ function createEnumLikeObject(obj, name, scene) {
|
|
|
243
263
|
* validateFileType({ type: 'image/png' }, ['image/png', 'image/jpeg']) // true
|
|
244
264
|
* validateFileType({ type: 'image/png' }, ['image/jpeg']) // false
|
|
245
265
|
* validateFileType({ type: 'image/png' }, ['image/*']) // true
|
|
266
|
+
* validateFileType({ name: '圖片.png', type: 'image/png' }, ['.png']) // true
|
|
246
267
|
* ```
|
|
247
268
|
*/ const validateFileType = (file, accepts)=>{
|
|
248
269
|
if (accepts.length === 0) return true;
|
|
249
270
|
// 獲取文件的MIME類型
|
|
250
271
|
const fileMimeType = file.type;
|
|
272
|
+
// 提取副檔名(含 .,且轉小寫)
|
|
273
|
+
const fileExt = file.name.includes('.') ? `.${file.name.split('.').pop().toLowerCase()}` : '';
|
|
251
274
|
return accepts.some((accept)=>{
|
|
275
|
+
if (accept.startsWith('.')) {
|
|
276
|
+
// 以副檔名檢查,忽略大小寫
|
|
277
|
+
return fileExt === accept.toLowerCase();
|
|
278
|
+
}
|
|
252
279
|
if (accept === fileMimeType) {
|
|
253
280
|
return true;
|
|
254
281
|
}
|
|
255
282
|
if (accept.endsWith('/*')) {
|
|
256
|
-
|
|
257
|
-
const fileCategory = fileMimeType.split('/')[0];
|
|
258
|
-
if (acceptedCategory === fileCategory) {
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
283
|
+
return accept.split('/')[0] === fileMimeType.split('/')[0];
|
|
261
284
|
}
|
|
262
285
|
return false;
|
|
263
286
|
});
|
|
@@ -284,32 +307,70 @@ function createEnumLikeObject(obj, name, scene) {
|
|
|
284
307
|
* getMimeType('xlsx') // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
285
308
|
* getMimeType('doc') // 'application/msword'
|
|
286
309
|
* getMimeType('docx') // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
310
|
+
*
|
|
311
|
+
* @deprecated use `parseFileInfoFromFilename` instead
|
|
287
312
|
*/ const getMimeType = (fileName)=>{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
const ext = (fileName.split('.').pop() || '').toLowerCase();
|
|
314
|
+
return MimeTypeMap[ext] ?? OtherMimeType;
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* 用來解析後端在 response header content-disposition 的內容
|
|
318
|
+
*
|
|
319
|
+
* 一般來說格式會有以下兩種
|
|
320
|
+
*
|
|
321
|
+
* - Content-Disposition: attachment; filename="file name.jpg"
|
|
322
|
+
* - Content-Disposition: attachment; filename*=UTF-8''file%20name2.jpg
|
|
323
|
+
*
|
|
324
|
+
* 如果格式正確就會取得檔案名稱(包含副檔名),優先取 filename* 內的內容
|
|
325
|
+
*
|
|
326
|
+
* @param disposition Content-Disposition
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
*
|
|
330
|
+
* parseFilenameFromDisposition('attachment; filename="file name1.jpg') // file name.jpg
|
|
331
|
+
* parseFilenameFromDisposition('attachment; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
|
|
332
|
+
* parseFilenameFromDisposition('attachment; filename="file name.jpg; filename*=UTF-8''file%20name2.jpg') // file name2.jpg
|
|
333
|
+
*/ const parseFilenameFromDisposition = (disposition)=>{
|
|
334
|
+
// 1. 先找 filename*
|
|
335
|
+
const filenameStarMatch = disposition.match(/filename\*\s*=\s*(?:UTF-8'')?([^;]+)/i);
|
|
336
|
+
if (filenameStarMatch && filenameStarMatch[1]) {
|
|
337
|
+
// 依 RFC 5987 格式(UTF-8''URL-ENCODED),要先 decode
|
|
338
|
+
try {
|
|
339
|
+
return decodeURIComponent(filenameStarMatch[1].replace(/(^['"]|['"]$)/g, ''));
|
|
340
|
+
} catch {
|
|
341
|
+
// fallback,如果 decode 失敗,直接傳回原值
|
|
342
|
+
return filenameStarMatch[1];
|
|
343
|
+
}
|
|
312
344
|
}
|
|
345
|
+
// 2. 沒有 filename*,再找 filename
|
|
346
|
+
const filenameMatch = disposition.match(/filename\s*=\s*("?)([^";]+)\1/i);
|
|
347
|
+
if (filenameMatch && filenameMatch[2]) {
|
|
348
|
+
return filenameMatch[2];
|
|
349
|
+
}
|
|
350
|
+
// 3. 都沒有則 undefined
|
|
351
|
+
return undefined;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* 解析 `filename` 回傳檔名、副檔名、MIME type
|
|
355
|
+
*
|
|
356
|
+
* @param filename 檔案名稱
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
*
|
|
360
|
+
* parseFileInfoFromFilename('image.jpg') // ['image', 'jpg', 'image/jpeg']
|
|
361
|
+
* parseFileInfoFromFilename('image') // ['image', '', 'application/octet-stream']
|
|
362
|
+
*/ const parseFileInfoFromFilename = (filename)=>{
|
|
363
|
+
const lastDot = filename.lastIndexOf('.');
|
|
364
|
+
if (lastDot === -1) return [
|
|
365
|
+
filename,
|
|
366
|
+
'',
|
|
367
|
+
OtherMimeType
|
|
368
|
+
]; // 沒有副檔名
|
|
369
|
+
return [
|
|
370
|
+
filename.slice(0, lastDot),
|
|
371
|
+
filename.slice(lastDot + 1),
|
|
372
|
+
MimeTypeMap[filename.slice(lastDot + 1)] ?? OtherMimeType
|
|
373
|
+
];
|
|
313
374
|
};
|
|
314
375
|
|
|
315
376
|
// const isProduction: boolean = process.env.NODE_ENV === 'production';
|
|
@@ -439,6 +500,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
|
|
|
439
500
|
}, {
|
|
440
501
|
...target
|
|
441
502
|
});
|
|
503
|
+
/**
|
|
504
|
+
* A utility function to deeply clone an object.
|
|
505
|
+
* @param obj - The object to clone.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
*
|
|
509
|
+
* const original = { a: 1, b: { c: 2 } };
|
|
510
|
+
* const cloned = deepClone(original);
|
|
511
|
+
* console.log(cloned); // { a: 1, b: { c: 2 } }
|
|
512
|
+
*
|
|
513
|
+
*/ const deepClone = (obj)=>{
|
|
514
|
+
if (obj === null || typeof obj !== 'object') {
|
|
515
|
+
return obj;
|
|
516
|
+
}
|
|
517
|
+
if (obj instanceof Date) {
|
|
518
|
+
return new Date(obj.getTime());
|
|
519
|
+
}
|
|
520
|
+
if (Array.isArray(obj)) {
|
|
521
|
+
return obj.map((item)=>deepClone(item));
|
|
522
|
+
}
|
|
523
|
+
const cloned = Object.keys(obj).reduce((acc, key)=>{
|
|
524
|
+
acc[key] = deepClone(obj[key]);
|
|
525
|
+
return acc;
|
|
526
|
+
}, {});
|
|
527
|
+
return cloned;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 將嵌套物件的所有屬性設為指定的布林值
|
|
532
|
+
* @param obj - 目標物件
|
|
533
|
+
* @param boolValue - 布林值
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* const obj = { a: { b: 1, c: 2 }, d: 3 };
|
|
537
|
+
* const result = setBooleanToNestedObject(obj, true);
|
|
538
|
+
* console.log(result); // { a: { b: true, c: true }, d: true }
|
|
539
|
+
*/ const setBooleanToNestedObject = (obj, boolValue)=>Object.entries(obj).reduce((acc, [key, value])=>{
|
|
540
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
541
|
+
// 如果是嵌套物件,遞歸處理
|
|
542
|
+
acc[key] = setBooleanToNestedObject(value, boolValue);
|
|
543
|
+
} else {
|
|
544
|
+
// 如果是基本類型,設為布林值
|
|
545
|
+
acc[key] = boolValue;
|
|
546
|
+
}
|
|
547
|
+
return acc;
|
|
548
|
+
}, {});
|
|
549
|
+
/**
|
|
550
|
+
* 深度合併配置物件的通用工具
|
|
551
|
+
*
|
|
552
|
+
* @param defaultConfig - 預設配置
|
|
553
|
+
* @param customConfig - 自訂配置
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
|
|
557
|
+
* mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
|
|
558
|
+
* mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
|
|
559
|
+
* mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
|
|
560
|
+
*/ const mergeConfig = (defaultConfig, customConfig = {})=>{
|
|
561
|
+
// 深拷貝預設配置以避免修改原始物件
|
|
562
|
+
const result = deepClone(defaultConfig);
|
|
563
|
+
return Object.entries(customConfig).reduce((acc, [key, sourceValue])=>{
|
|
564
|
+
const targetValue = acc[key];
|
|
565
|
+
// 如果來源值為 null 或 undefined,跳過
|
|
566
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
567
|
+
return acc;
|
|
568
|
+
}
|
|
569
|
+
// 如果目標物件中對應的值是物件,且來源值是布林值
|
|
570
|
+
// 這是特殊情況:用布林值覆蓋整個嵌套物件
|
|
571
|
+
if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'boolean') {
|
|
572
|
+
// 將嵌套物件的所有屬性設為該布林值
|
|
573
|
+
acc[key] = setBooleanToNestedObject(targetValue, sourceValue);
|
|
574
|
+
} else if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
575
|
+
acc[key] = mergeConfig(targetValue, sourceValue);
|
|
576
|
+
} else {
|
|
577
|
+
acc[key] = sourceValue;
|
|
578
|
+
}
|
|
579
|
+
return acc;
|
|
580
|
+
}, result);
|
|
581
|
+
};
|
|
442
582
|
|
|
443
583
|
/**
|
|
444
584
|
* debounce function
|
|
@@ -848,8 +988,7 @@ const FILE_SIZE_UNITS = [
|
|
|
848
988
|
*
|
|
849
989
|
* isEmail().test('123') // false
|
|
850
990
|
* isEmail().test('123@gmail.com') // true
|
|
851
|
-
*/ const isEmail = ()
|
|
852
|
-
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
|
|
991
|
+
*/ const isEmail = ()=>/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\\-]*\.)+[A-Z]{2,}$/i;
|
|
853
992
|
/**
|
|
854
993
|
* 日期字串
|
|
855
994
|
*
|
|
@@ -1048,6 +1187,29 @@ const FILE_SIZE_UNITS = [
|
|
|
1048
1187
|
// 再用 dayjs 驗證是否為有效日期
|
|
1049
1188
|
return dayjs(dateString, format, true).isValid();
|
|
1050
1189
|
};
|
|
1190
|
+
/**
|
|
1191
|
+
* 判斷 `value` 是否為 `null` 或 `undefined`
|
|
1192
|
+
*
|
|
1193
|
+
* @param value 值
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* isNil(null); // true
|
|
1197
|
+
* isNil(undefined); // true
|
|
1198
|
+
* isNil(0); // false
|
|
1199
|
+
* isNil(''); // false
|
|
1200
|
+
* isNil(false); // false
|
|
1201
|
+
* isNil({}); // false
|
|
1202
|
+
*
|
|
1203
|
+
* // TypeScript 型別縮窄應用
|
|
1204
|
+
* function example(input?: string | null) {
|
|
1205
|
+
* if (isNil(input)) {
|
|
1206
|
+
* // input 型別會自動縮窄成 null | undefined
|
|
1207
|
+
* return 'No value';
|
|
1208
|
+
* }
|
|
1209
|
+
* // input 型別自動為 string
|
|
1210
|
+
* return input.toUpperCase();
|
|
1211
|
+
* }
|
|
1212
|
+
*/ const isNil = (value)=>value === null || value === undefined;
|
|
1051
1213
|
|
|
1052
1214
|
/**
|
|
1053
1215
|
* Creates a strongly-typed React Context and Provider pair for data sharing.
|
|
@@ -1057,7 +1219,7 @@ const FILE_SIZE_UNITS = [
|
|
|
1057
1219
|
* and a Provider component for supplying context values.
|
|
1058
1220
|
*
|
|
1059
1221
|
* @template T - The value type for the context.
|
|
1060
|
-
* @returns {
|
|
1222
|
+
* @returns {object} An object containing:
|
|
1061
1223
|
* - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
|
|
1062
1224
|
* - DataProvider: A Provider component to wrap your component tree and supply the context value.
|
|
1063
1225
|
*
|
|
@@ -1224,4 +1386,4 @@ function mergeRefs(refs) {
|
|
|
1224
1386
|
return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
|
|
1225
1387
|
};
|
|
1226
1388
|
|
|
1227
|
-
export { ByteSize, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createDataContext, createEnumLikeObject, debounce, deepMerge, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, 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, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useValue, validTaxId, validateDateString, validateFileType, wait };
|
|
1389
|
+
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createDataContext, createEnumLikeObject, debounce, deepClone, deepMerge, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, 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, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useValue, validTaxId, validateDateString, validateFileType, wait };
|
package/dist/es/types.d.mts
CHANGED
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gateweb/react-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "React Utils for GateWeb",
|
|
5
5
|
"homepage": "https://github.com/GatewebSolutions/react-utils",
|
|
6
6
|
"files": [
|
|
@@ -36,33 +36,38 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"dayjs": "^1.11.13",
|
|
39
|
-
"react": "^19.
|
|
40
|
-
"react-dom": "^19.
|
|
39
|
+
"react": "^19.1.0",
|
|
40
|
+
"react-dom": "^19.1.0",
|
|
41
41
|
"use-sync-external-store": "^1.5.0",
|
|
42
|
-
"zustand": "^5.0.
|
|
42
|
+
"zustand": "^5.0.6"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@commitlint/cli": "^19.
|
|
46
|
-
"@commitlint/config-conventional": "^19.
|
|
47
|
-
"@gateweb/eslint-config-gateweb": "^1.
|
|
45
|
+
"@commitlint/cli": "^19.8.1",
|
|
46
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
47
|
+
"@gateweb/eslint-config-gateweb": "^2.1.1",
|
|
48
48
|
"@testing-library/jest-dom": "^6.6.3",
|
|
49
|
-
"@testing-library/react": "^16.
|
|
50
|
-
"@types/jest": "^
|
|
51
|
-
"@types/node": "^
|
|
52
|
-
"@types/react": "^19.
|
|
53
|
-
"@vitest/coverage-v8": "^2.
|
|
54
|
-
"@vitest/ui": "2.
|
|
49
|
+
"@testing-library/react": "^16.3.0",
|
|
50
|
+
"@types/jest": "^30.0.0",
|
|
51
|
+
"@types/node": "^24.1.0",
|
|
52
|
+
"@types/react": "^19.1.8",
|
|
53
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
54
|
+
"@vitest/ui": "3.2.4",
|
|
55
55
|
"bunchee": "^5.6.1",
|
|
56
|
-
"eslint": "^
|
|
57
|
-
"husky": "^9.1.
|
|
58
|
-
"jest": "^
|
|
59
|
-
"jest-environment-jsdom": "^
|
|
60
|
-
"lint-staged": "^
|
|
61
|
-
"prettier": "^3.
|
|
62
|
-
"ts-jest": "^29.
|
|
56
|
+
"eslint": "^9.31.0",
|
|
57
|
+
"husky": "^9.1.7",
|
|
58
|
+
"jest": "^30.0.5",
|
|
59
|
+
"jest-environment-jsdom": "^30.0.5",
|
|
60
|
+
"lint-staged": "^16.1.2",
|
|
61
|
+
"prettier": "^3.6.2",
|
|
62
|
+
"ts-jest": "^29.4.0",
|
|
63
63
|
"ts-node": "^10.9.2",
|
|
64
|
-
"typescript": "^5.
|
|
65
|
-
"vitest": "^2.
|
|
64
|
+
"typescript": "^5.8.3",
|
|
65
|
+
"vitest": "^3.2.4"
|
|
66
|
+
},
|
|
67
|
+
"commitlint": {
|
|
68
|
+
"extends": [
|
|
69
|
+
"@commitlint/config-conventional"
|
|
70
|
+
]
|
|
66
71
|
},
|
|
67
72
|
"scripts": {
|
|
68
73
|
"run-code": "ts-node src/period.ts",
|