@gateweb/react-utils 1.13.0 → 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 CHANGED
@@ -1,74 +1,162 @@
1
- # react-utils
1
+ # @gateweb/react-utils
2
2
 
3
- **_react-utils_** is a collection of utility functions for GateWeb's React projects.
3
+ **`@gateweb/react-utils`** is a comprehensive, TypeScript-first utility library designed to streamline development in React projects at GateWeb. It provides a collection of robust, well-tested, and reusable functions and hooks, covering everything from date manipulation and type conversion to custom hooks and web APIs.
4
4
 
5
- ## Installation
5
+ [![npm version](https://img.shields.io/npm/v/@gateweb/react-utils.svg)](https://www.npmjs.com/package/@gateweb/react-utils)
6
+
7
+ ## ✨ Features
8
+
9
+ - **React Hooks**: A collection of useful hooks like `useCountdown` and `useValue`.
10
+ - **Date Utilities**: Powerful and flexible functions for handling dates and time periods, built on top of `dayjs`.
11
+ - **Core Helpers**: A wide range of utilities for type conversion (`bytes`, `searchParams`), case style formatting, object manipulation, and more.
12
+ - **Web APIs**: Simplified functions for common web tasks like file downloads (`download`) and managing browser storage (`webStorage`).
13
+ - **Validation**: A set of predefined regex patterns and validation functions for common data types.
14
+ - **TypeScript First**: Fully written in TypeScript, providing excellent type safety and autocompletion out of the box.
15
+ - **Lightweight & Tree-Shakable**: Built with `bunchee` to ensure minimal bundle size.
16
+
17
+ ## 📦 Installation
6
18
 
7
19
  ```bash
20
+ # With pnpm (recommended)
21
+ pnpm add @gateweb/react-utils
22
+
23
+ # With npm
8
24
  npm install @gateweb/react-utils
9
- # or
25
+
26
+ # With yarn
10
27
  yarn add @gateweb/react-utils
11
- # or
12
- pnpm add @gateweb/react-utils
13
28
  ```
14
29
 
15
- ## Usage
30
+ ## 🚀 Usage
31
+
32
+ Here are some examples of the most common utilities.
33
+
34
+ ### React Hooks
35
+
36
+ #### `useCountdown`
37
+
38
+ A hook to manage a countdown timer.
16
39
 
17
40
  ```tsx
18
- import { formatAmount } from '@gateweb/react-utils';
41
+ import { useCountdown } from '@gateweb/react-utils';
42
+
43
+ function MyComponent() {
44
+ const { count, start, stop, reset } = useCountdown({ initialValue: 60 });
45
+
46
+ return (
47
+ <div>
48
+ <p>Countdown: {count}</p>
49
+ <button onClick={start}>Start</button>
50
+ <button onClick={stop}>Stop</button>
51
+ <button onClick={reset}>Reset</button>
52
+ </div>
53
+ );
54
+ }
19
55
  ```
20
56
 
21
- ## How to Release a New Version
57
+ ### Date Utilities
58
+
59
+ #### `formatDate`
60
+
61
+ Format a date object or string into a custom format.
22
62
 
23
- ### Manually Bump the Version
63
+ ```ts
64
+ import { formatDate } from '@gateweb/react-utils';
24
65
 
25
- 1. Update the version in `package.json`
66
+ const formattedDate = formatDate(new Date(), 'YYYY-MM-DD');
67
+ console.log(formattedDate); // e.g., "2023-10-27"
68
+ ```
69
+
70
+ #### `getPeriod`
26
71
 
27
- ```diff
28
- # e.g. bump the version from 0.1.0 to 0.1.1
29
- {
30
- "name": "@gateweb/react-utils",
31
- - "version": "0.1.0"
32
- + "version": "0.1.1"
33
- }
34
- ```
72
+ Get a predefined date period, like "last 7 days".
35
73
 
36
- 2. Install the dependencies and build the package
74
+ ```ts
75
+ import { getPeriod } from '@gateweb/react-utils';
76
+
77
+ const last7Days = getPeriod('last_7_days');
78
+ console.log(last7Days); // { startDate: Dayjs, endDate: Dayjs }
79
+ ```
37
80
 
38
- ```sh
39
- pnpm install
40
- pnpm build
41
- ```
81
+ ### Core Utilities
82
+
83
+ #### `toCamelCase`
84
+
85
+ Convert a string from snake_case or kebab-case to camelCase.
86
+
87
+ ```ts
88
+ import { toCamelCase } from '@gateweb/react-utils';
89
+
90
+ const camel = toCamelCase('hello_world-example');
91
+ console.log(camel); // "helloWorldExample"
92
+ ```
93
+
94
+ #### `bytesToSize`
95
+
96
+ Convert bytes to a human-readable format (KB, MB, GB).
97
+
98
+ ```ts
99
+ import { bytesToSize } from '@gateweb/react-utils';
100
+
101
+ const size = bytesToSize(1024 * 1024 * 5); // 5MB
102
+ console.log(size); // "5 MB"
103
+ ```
104
+
105
+ ### Web APIs
106
+
107
+ #### `download`
108
+
109
+ A simple utility to trigger a file download in the browser.
110
+
111
+ ```ts
112
+ import { download } from '@gateweb/react-utils';
113
+
114
+ function handleDownload() {
115
+ const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
116
+ download(blob, 'hello.txt');
117
+ }
118
+ ```
42
119
 
43
- 3. login to npm
120
+ ## 🤝 Contributing
44
121
 
45
- ```sh
46
- # make sure you have an npm account
47
- pnpm login
48
- # npm notice Log in on https://registry.npmjs.org/
49
- # Login at:
50
- # https://www.npmjs.com/login?next=/login/cli/390e514d-7aa5-4ee7-a13a-b17e6dd64518
51
- # Press ENTER to open in the browser...
52
- ```
122
+ Contributions are welcome! To ensure a smooth development process, please follow these steps:
53
123
 
54
- 4. Publish the package
124
+ 1. **Fork & Clone**: Fork the repository and clone it to your local machine.
125
+ 2. **Set Up**: This project uses `pnpm` as the package manager. Install dependencies with:
126
+ ```bash
127
+ pnpm install
128
+ ```
129
+ 3. **Develop**: Create a new branch for your feature or bug fix.
130
+ 4. **Test**: Write tests for your changes and ensure all existing tests pass.
55
131
 
56
- ```sh
57
- pnpm publish --access public --no-git-checks
58
- ```
132
+ ```bash
133
+ # Run all tests
134
+ pnpm test
59
135
 
60
- 5. Check the published package on npmjs.com
136
+ # Run tests in watch mode
137
+ pnpm test:watch
138
+ ```
61
139
 
62
- [@gateweb/react-utils](https://www.npmjs.com/package/@gateweb/react-utils)
140
+ 5. **Submit a Pull Request**: Push your changes to your fork and open a pull request to the `main` branch. Your code will be automatically linted and formatted upon commit.
63
141
 
64
- ### Automatically Bump the Version
142
+ ## Publishing (For Maintainers)
65
143
 
66
- - When you merge a pull request to the `main` branch, the GitHub Action will automatically bump the version and publish the package to npm.
144
+ ### Manual Release
67
145
 
68
- - The version will follow the [Semantic Versioning](https://semver.org/) rules.
146
+ 1. Update the version in `package.json`.
147
+ 2. Install dependencies and build the package:
148
+ ```sh
149
+ pnpm install && pnpm build
150
+ ```
151
+ 3. Log in to npm:
152
+ ```sh
153
+ pnpm login
154
+ ```
155
+ 4. Publish the package:
156
+ ```sh
157
+ pnpm publish --access public --no-git-checks
158
+ ```
69
159
 
70
- ## Notes
160
+ ### Automatic Release
71
161
 
72
- - You should use `pnpm` to develop this package.
73
- - This package is written in TypeScript and supports TypeScript out of the box.
74
- - This package is built with [bunchee](https://github.com/huozhi/bunchee) which is a zero-config build tool for TypeScript packages based on Rollup.
162
+ When a pull request is merged into the `main` branch, the `release` GitHub Action will automatically bump the version, create a changelog, and publish the new version to npm. The version bump follows [Semantic Versioning](https.semver.org/) based on the commit messages (e.g., `feat:` for minor, `fix:` for patch).
@@ -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;
@@ -576,6 +579,23 @@ declare function invariant(condition: any, message?: string | (() => string)): a
576
579
  */
577
580
  declare const isServer: () => boolean;
578
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
+
579
599
  /**
580
600
  * 將物件中的某些 key 排除
581
601
  *
@@ -671,6 +691,18 @@ type RequiredBy<T, K extends keyof T, RemoveUndefined extends boolean = false> =
671
691
  type PartialBy<T, K extends keyof T, RemoveUndefined extends boolean = false> = Omit<T, K> & {
672
692
  [P in K]?: RemoveUndefined extends true ? Exclude<T[P], undefined> : T[P];
673
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;
674
706
 
675
707
  /**
676
708
  * debounce function
@@ -983,6 +1015,30 @@ declare const validTaxId: (taxId: string) => boolean;
983
1015
  * validateDateString('20210201', 'YYYYMM') // false
984
1016
  */
985
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;
986
1042
 
987
1043
  type TQueryProps<Q> = {
988
1044
  /**
@@ -1042,7 +1098,7 @@ declare const useQueryContext: <Q>() => <T>(selector: (state: TQueryState<Q>) =>
1042
1098
  * and a Provider component for supplying context values.
1043
1099
  *
1044
1100
  * @template T - The value type for the context.
1045
- * @returns {Object} An object containing:
1101
+ * @returns {object} An object containing:
1046
1102
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1047
1103
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1048
1104
  *
@@ -1204,4 +1260,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
1204
1260
  */
1205
1261
  declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
1206
1262
 
1207
- export { ByteSize, type MimeTypeExtension, MimeTypeMap, type MimeTypeValue, OtherMimeType, 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, parseFileInfoFromFilename, parseFilenameFromDisposition, 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 };
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
 
@@ -507,6 +507,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
507
507
  }, {
508
508
  ...target
509
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
+ };
510
589
 
511
590
  /**
512
591
  * debounce function
@@ -916,8 +995,7 @@ const FILE_SIZE_UNITS = [
916
995
  *
917
996
  * isEmail().test('123') // false
918
997
  * isEmail().test('123@gmail.com') // true
919
- */ const isEmail = ()=>// eslint-disable-next-line
920
- /^(?!\.)(?!.*\.\.)([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;
921
999
  /**
922
1000
  * 日期字串
923
1001
  *
@@ -1116,6 +1194,29 @@ const FILE_SIZE_UNITS = [
1116
1194
  // 再用 dayjs 驗證是否為有效日期
1117
1195
  return dayjs__default.default(dateString, format, true).isValid();
1118
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;
1119
1220
 
1120
1221
  /**
1121
1222
  * Creates a strongly-typed React Context and Provider pair for data sharing.
@@ -1125,7 +1226,7 @@ const FILE_SIZE_UNITS = [
1125
1226
  * and a Provider component for supplying context values.
1126
1227
  *
1127
1228
  * @template T - The value type for the context.
1128
- * @returns {Object} An object containing:
1229
+ * @returns {object} An object containing:
1129
1230
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1130
1231
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1131
1232
  *
@@ -1310,6 +1411,7 @@ exports.convertBytes = convertBytes;
1310
1411
  exports.createDataContext = createDataContext;
1311
1412
  exports.createEnumLikeObject = createEnumLikeObject;
1312
1413
  exports.debounce = debounce;
1414
+ exports.deepClone = deepClone;
1313
1415
  exports.deepMerge = deepMerge;
1314
1416
  exports.extractEnumLikeObject = extractEnumLikeObject;
1315
1417
  exports.fakeApi = fakeApi;
@@ -1326,6 +1428,7 @@ exports.isDateTimeString = isDateTimeString;
1326
1428
  exports.isEmail = isEmail;
1327
1429
  exports.isEnglish = isEnglish;
1328
1430
  exports.isEqual = isEqual;
1431
+ exports.isNil = isNil;
1329
1432
  exports.isNonZeroStart = isNonZeroStart;
1330
1433
  exports.isNumber = isNumber;
1331
1434
  exports.isNumberAtLeastN = isNumberAtLeastN;
@@ -1337,6 +1440,7 @@ exports.isTWPhone = isTWPhone;
1337
1440
  exports.isTimeString = isTimeString;
1338
1441
  exports.isValidPassword = isValidPassword;
1339
1442
  exports.maskString = maskString;
1443
+ exports.mergeConfig = mergeConfig;
1340
1444
  exports.mergeRefs = mergeRefs;
1341
1445
  exports.objectToSearchParams = objectToSearchParams;
1342
1446
  exports.omit = omit;
@@ -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;
@@ -576,6 +579,23 @@ declare function invariant(condition: any, message?: string | (() => string)): a
576
579
  */
577
580
  declare const isServer: () => boolean;
578
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
+
579
599
  /**
580
600
  * 將物件中的某些 key 排除
581
601
  *
@@ -671,6 +691,18 @@ type RequiredBy<T, K extends keyof T, RemoveUndefined extends boolean = false> =
671
691
  type PartialBy<T, K extends keyof T, RemoveUndefined extends boolean = false> = Omit<T, K> & {
672
692
  [P in K]?: RemoveUndefined extends true ? Exclude<T[P], undefined> : T[P];
673
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;
674
706
 
675
707
  /**
676
708
  * debounce function
@@ -983,6 +1015,30 @@ declare const validTaxId: (taxId: string) => boolean;
983
1015
  * validateDateString('20210201', 'YYYYMM') // false
984
1016
  */
985
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;
986
1042
 
987
1043
  type TQueryProps<Q> = {
988
1044
  /**
@@ -1042,7 +1098,7 @@ declare const useQueryContext: <Q>() => <T>(selector: (state: TQueryState<Q>) =>
1042
1098
  * and a Provider component for supplying context values.
1043
1099
  *
1044
1100
  * @template T - The value type for the context.
1045
- * @returns {Object} An object containing:
1101
+ * @returns {object} An object containing:
1046
1102
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1047
1103
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1048
1104
  *
@@ -1204,4 +1260,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
1204
1260
  */
1205
1261
  declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
1206
1262
 
1207
- export { ByteSize, type MimeTypeExtension, MimeTypeMap, type MimeTypeValue, OtherMimeType, 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, parseFileInfoFromFilename, parseFilenameFromDisposition, 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 };
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-vG-bXFYm.mjs';
3
- import React, { useContext, useMemo, createContext, useState, useCallback } from '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-Pd-loNCg.mjs';
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',
@@ -500,6 +500,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
500
500
  }, {
501
501
  ...target
502
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
+ };
503
582
 
504
583
  /**
505
584
  * debounce function
@@ -909,8 +988,7 @@ const FILE_SIZE_UNITS = [
909
988
  *
910
989
  * isEmail().test('123') // false
911
990
  * isEmail().test('123@gmail.com') // true
912
- */ const isEmail = ()=>// eslint-disable-next-line
913
- /^(?!\.)(?!.*\.\.)([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;
914
992
  /**
915
993
  * 日期字串
916
994
  *
@@ -1109,6 +1187,29 @@ const FILE_SIZE_UNITS = [
1109
1187
  // 再用 dayjs 驗證是否為有效日期
1110
1188
  return dayjs(dateString, format, true).isValid();
1111
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;
1112
1213
 
1113
1214
  /**
1114
1215
  * Creates a strongly-typed React Context and Provider pair for data sharing.
@@ -1118,7 +1219,7 @@ const FILE_SIZE_UNITS = [
1118
1219
  * and a Provider component for supplying context values.
1119
1220
  *
1120
1221
  * @template T - The value type for the context.
1121
- * @returns {Object} An object containing:
1222
+ * @returns {object} An object containing:
1122
1223
  * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1123
1224
  * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1124
1225
  *
@@ -1285,4 +1386,4 @@ function mergeRefs(refs) {
1285
1386
  return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
1286
1387
  };
1287
1388
 
1288
- export { ByteSize, MimeTypeMap, OtherMimeType, 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, parseFileInfoFromFilename, parseFilenameFromDisposition, 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 };
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import React, { useRef, useContext, createContext } from 'react';
2
+ import React, { useRef, createContext, useContext } from 'react';
3
3
  import { createStore } from 'zustand';
4
4
  import { useStoreWithEqualityFn } from 'zustand/traditional';
5
5
 
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gateweb/react-utils",
3
- "version": "1.13.0",
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.0.0",
40
- "react-dom": "^19.0.0",
39
+ "react": "^19.1.0",
40
+ "react-dom": "^19.1.0",
41
41
  "use-sync-external-store": "^1.5.0",
42
- "zustand": "^5.0.3"
42
+ "zustand": "^5.0.6"
43
43
  },
44
44
  "devDependencies": {
45
- "@commitlint/cli": "^19.5.0",
46
- "@commitlint/config-conventional": "^19.5.0",
47
- "@gateweb/eslint-config-gateweb": "^1.0.6",
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.2.0",
50
- "@types/jest": "^29.5.13",
51
- "@types/node": "^22.7.7",
52
- "@types/react": "^19.0.8",
53
- "@vitest/coverage-v8": "^2.1.4",
54
- "@vitest/ui": "2.1.4",
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": "^8",
57
- "husky": "^9.1.6",
58
- "jest": "^29.7.0",
59
- "jest-environment-jsdom": "^29.7.0",
60
- "lint-staged": "^15.2.10",
61
- "prettier": "^3.3.3",
62
- "ts-jest": "^29.2.5",
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.6.3",
65
- "vitest": "^2.1.4"
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",