@gateweb/react-utils 1.13.0 → 1.14.1
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 +61 -4
- package/dist/cjs/index.js +109 -9
- 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 +61 -4
- package/dist/es/index.mjs +109 -12
- 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/README.md
CHANGED
|
@@ -1,74 +1,162 @@
|
|
|
1
|
-
# react-utils
|
|
1
|
+
# @gateweb/react-utils
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
[](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
|
-
|
|
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 {
|
|
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
|
-
|
|
57
|
+
### Date Utilities
|
|
58
|
+
|
|
59
|
+
#### `formatDate`
|
|
60
|
+
|
|
61
|
+
Format a date object or string into a custom format.
|
|
22
62
|
|
|
23
|
-
|
|
63
|
+
```ts
|
|
64
|
+
import { formatDate } from '@gateweb/react-utils';
|
|
24
65
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
120
|
+
## 🤝 Contributing
|
|
44
121
|
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
132
|
+
```bash
|
|
133
|
+
# Run all tests
|
|
134
|
+
pnpm test
|
|
59
135
|
|
|
60
|
-
|
|
136
|
+
# Run tests in watch mode
|
|
137
|
+
pnpm test:watch
|
|
138
|
+
```
|
|
61
139
|
|
|
62
|
-
|
|
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
|
-
|
|
142
|
+
## Publishing (For Maintainers)
|
|
65
143
|
|
|
66
|
-
|
|
144
|
+
### Manual Release
|
|
67
145
|
|
|
68
|
-
|
|
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
|
-
|
|
160
|
+
### Automatic Release
|
|
71
161
|
|
|
72
|
-
|
|
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).
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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 {
|
|
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,
|
|
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-
|
|
8
|
+
var webStorageClient = require('./webStorage-client-DHr9PcPl.js');
|
|
9
9
|
|
|
10
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
|
|
@@ -200,10 +200,6 @@ function createEnumLikeObject(obj, name, scene) {
|
|
|
200
200
|
function getLabel(value) {
|
|
201
201
|
const targetItem = list.find((item)=>item.value === value);
|
|
202
202
|
if (!targetItem) return String(value);
|
|
203
|
-
if ('scenes' in targetItem && scene) {
|
|
204
|
-
const sceneObj = targetItem.scenes?.[scene];
|
|
205
|
-
return sceneObj && sceneObj.label || String(value);
|
|
206
|
-
}
|
|
207
203
|
if ('label' in targetItem) return targetItem.label;
|
|
208
204
|
return String(value);
|
|
209
205
|
}
|
|
@@ -507,6 +503,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
|
|
|
507
503
|
}, {
|
|
508
504
|
...target
|
|
509
505
|
});
|
|
506
|
+
/**
|
|
507
|
+
* A utility function to deeply clone an object.
|
|
508
|
+
* @param obj - The object to clone.
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
*
|
|
512
|
+
* const original = { a: 1, b: { c: 2 } };
|
|
513
|
+
* const cloned = deepClone(original);
|
|
514
|
+
* console.log(cloned); // { a: 1, b: { c: 2 } }
|
|
515
|
+
*
|
|
516
|
+
*/ const deepClone = (obj)=>{
|
|
517
|
+
if (obj === null || typeof obj !== 'object') {
|
|
518
|
+
return obj;
|
|
519
|
+
}
|
|
520
|
+
if (obj instanceof Date) {
|
|
521
|
+
return new Date(obj.getTime());
|
|
522
|
+
}
|
|
523
|
+
if (Array.isArray(obj)) {
|
|
524
|
+
return obj.map((item)=>deepClone(item));
|
|
525
|
+
}
|
|
526
|
+
const cloned = Object.keys(obj).reduce((acc, key)=>{
|
|
527
|
+
acc[key] = deepClone(obj[key]);
|
|
528
|
+
return acc;
|
|
529
|
+
}, {});
|
|
530
|
+
return cloned;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* 將嵌套物件的所有屬性設為指定的布林值
|
|
535
|
+
* @param obj - 目標物件
|
|
536
|
+
* @param boolValue - 布林值
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* const obj = { a: { b: 1, c: 2 }, d: 3 };
|
|
540
|
+
* const result = setBooleanToNestedObject(obj, true);
|
|
541
|
+
* console.log(result); // { a: { b: true, c: true }, d: true }
|
|
542
|
+
*/ const setBooleanToNestedObject = (obj, boolValue)=>Object.entries(obj).reduce((acc, [key, value])=>{
|
|
543
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
544
|
+
// 如果是嵌套物件,遞歸處理
|
|
545
|
+
acc[key] = setBooleanToNestedObject(value, boolValue);
|
|
546
|
+
} else {
|
|
547
|
+
// 如果是基本類型,設為布林值
|
|
548
|
+
acc[key] = boolValue;
|
|
549
|
+
}
|
|
550
|
+
return acc;
|
|
551
|
+
}, {});
|
|
552
|
+
/**
|
|
553
|
+
* 深度合併配置物件的通用工具
|
|
554
|
+
*
|
|
555
|
+
* @param defaultConfig - 預設配置
|
|
556
|
+
* @param customConfig - 自訂配置
|
|
557
|
+
*
|
|
558
|
+
* @example
|
|
559
|
+
* const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
|
|
560
|
+
* mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
|
|
561
|
+
* mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
|
|
562
|
+
* mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
|
|
563
|
+
*/ const mergeConfig = (defaultConfig, customConfig = {})=>{
|
|
564
|
+
// 深拷貝預設配置以避免修改原始物件
|
|
565
|
+
const result = deepClone(defaultConfig);
|
|
566
|
+
return Object.entries(customConfig).reduce((acc, [key, sourceValue])=>{
|
|
567
|
+
const targetValue = acc[key];
|
|
568
|
+
// 如果來源值為 null 或 undefined,跳過
|
|
569
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
570
|
+
return acc;
|
|
571
|
+
}
|
|
572
|
+
// 如果目標物件中對應的值是物件,且來源值是布林值
|
|
573
|
+
// 這是特殊情況:用布林值覆蓋整個嵌套物件
|
|
574
|
+
if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'boolean') {
|
|
575
|
+
// 將嵌套物件的所有屬性設為該布林值
|
|
576
|
+
acc[key] = setBooleanToNestedObject(targetValue, sourceValue);
|
|
577
|
+
} else if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
578
|
+
acc[key] = mergeConfig(targetValue, sourceValue);
|
|
579
|
+
} else {
|
|
580
|
+
acc[key] = sourceValue;
|
|
581
|
+
}
|
|
582
|
+
return acc;
|
|
583
|
+
}, result);
|
|
584
|
+
};
|
|
510
585
|
|
|
511
586
|
/**
|
|
512
587
|
* debounce function
|
|
@@ -808,7 +883,7 @@ const transformObjectKey = (obj, transformFunName)=>{
|
|
|
808
883
|
*
|
|
809
884
|
* formatAmount(1234567) // '1,234,567'
|
|
810
885
|
*/ const formatAmount = (num)=>{
|
|
811
|
-
if (typeof num !== 'number') return '';
|
|
886
|
+
if (typeof num !== 'number' || isNaN(num)) return '';
|
|
812
887
|
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
|
813
888
|
};
|
|
814
889
|
/**
|
|
@@ -916,8 +991,7 @@ const FILE_SIZE_UNITS = [
|
|
|
916
991
|
*
|
|
917
992
|
* isEmail().test('123') // false
|
|
918
993
|
* isEmail().test('123@gmail.com') // true
|
|
919
|
-
*/ const isEmail = ()
|
|
920
|
-
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
|
|
994
|
+
*/ const isEmail = ()=>/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\\-]*\.)+[A-Z]{2,}$/i;
|
|
921
995
|
/**
|
|
922
996
|
* 日期字串
|
|
923
997
|
*
|
|
@@ -1116,6 +1190,29 @@ const FILE_SIZE_UNITS = [
|
|
|
1116
1190
|
// 再用 dayjs 驗證是否為有效日期
|
|
1117
1191
|
return dayjs__default.default(dateString, format, true).isValid();
|
|
1118
1192
|
};
|
|
1193
|
+
/**
|
|
1194
|
+
* 判斷 `value` 是否為 `null` 或 `undefined`
|
|
1195
|
+
*
|
|
1196
|
+
* @param value 值
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* isNil(null); // true
|
|
1200
|
+
* isNil(undefined); // true
|
|
1201
|
+
* isNil(0); // false
|
|
1202
|
+
* isNil(''); // false
|
|
1203
|
+
* isNil(false); // false
|
|
1204
|
+
* isNil({}); // false
|
|
1205
|
+
*
|
|
1206
|
+
* // TypeScript 型別縮窄應用
|
|
1207
|
+
* function example(input?: string | null) {
|
|
1208
|
+
* if (isNil(input)) {
|
|
1209
|
+
* // input 型別會自動縮窄成 null | undefined
|
|
1210
|
+
* return 'No value';
|
|
1211
|
+
* }
|
|
1212
|
+
* // input 型別自動為 string
|
|
1213
|
+
* return input.toUpperCase();
|
|
1214
|
+
* }
|
|
1215
|
+
*/ const isNil = (value)=>value === null || value === undefined;
|
|
1119
1216
|
|
|
1120
1217
|
/**
|
|
1121
1218
|
* Creates a strongly-typed React Context and Provider pair for data sharing.
|
|
@@ -1125,7 +1222,7 @@ const FILE_SIZE_UNITS = [
|
|
|
1125
1222
|
* and a Provider component for supplying context values.
|
|
1126
1223
|
*
|
|
1127
1224
|
* @template T - The value type for the context.
|
|
1128
|
-
* @returns {
|
|
1225
|
+
* @returns {object} An object containing:
|
|
1129
1226
|
* - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
|
|
1130
1227
|
* - DataProvider: A Provider component to wrap your component tree and supply the context value.
|
|
1131
1228
|
*
|
|
@@ -1310,6 +1407,7 @@ exports.convertBytes = convertBytes;
|
|
|
1310
1407
|
exports.createDataContext = createDataContext;
|
|
1311
1408
|
exports.createEnumLikeObject = createEnumLikeObject;
|
|
1312
1409
|
exports.debounce = debounce;
|
|
1410
|
+
exports.deepClone = deepClone;
|
|
1313
1411
|
exports.deepMerge = deepMerge;
|
|
1314
1412
|
exports.extractEnumLikeObject = extractEnumLikeObject;
|
|
1315
1413
|
exports.fakeApi = fakeApi;
|
|
@@ -1326,6 +1424,7 @@ exports.isDateTimeString = isDateTimeString;
|
|
|
1326
1424
|
exports.isEmail = isEmail;
|
|
1327
1425
|
exports.isEnglish = isEnglish;
|
|
1328
1426
|
exports.isEqual = isEqual;
|
|
1427
|
+
exports.isNil = isNil;
|
|
1329
1428
|
exports.isNonZeroStart = isNonZeroStart;
|
|
1330
1429
|
exports.isNumber = isNumber;
|
|
1331
1430
|
exports.isNumberAtLeastN = isNumberAtLeastN;
|
|
@@ -1337,6 +1436,7 @@ exports.isTWPhone = isTWPhone;
|
|
|
1337
1436
|
exports.isTimeString = isTimeString;
|
|
1338
1437
|
exports.isValidPassword = isValidPassword;
|
|
1339
1438
|
exports.maskString = maskString;
|
|
1439
|
+
exports.mergeConfig = mergeConfig;
|
|
1340
1440
|
exports.mergeRefs = mergeRefs;
|
|
1341
1441
|
exports.objectToSearchParams = objectToSearchParams;
|
|
1342
1442
|
exports.omit = omit;
|
package/dist/cjs/types.d.ts
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/dist/es/index.d.mts
CHANGED
|
@@ -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
|
-
*
|
|
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 {
|
|
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,
|
|
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-
|
|
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',
|
|
@@ -193,10 +193,6 @@ function createEnumLikeObject(obj, name, scene) {
|
|
|
193
193
|
function getLabel(value) {
|
|
194
194
|
const targetItem = list.find((item)=>item.value === value);
|
|
195
195
|
if (!targetItem) return String(value);
|
|
196
|
-
if ('scenes' in targetItem && scene) {
|
|
197
|
-
const sceneObj = targetItem.scenes?.[scene];
|
|
198
|
-
return sceneObj && sceneObj.label || String(value);
|
|
199
|
-
}
|
|
200
196
|
if ('label' in targetItem) return targetItem.label;
|
|
201
197
|
return String(value);
|
|
202
198
|
}
|
|
@@ -500,6 +496,85 @@ const isObject = (value)=>value !== null && typeof value === 'object';
|
|
|
500
496
|
}, {
|
|
501
497
|
...target
|
|
502
498
|
});
|
|
499
|
+
/**
|
|
500
|
+
* A utility function to deeply clone an object.
|
|
501
|
+
* @param obj - The object to clone.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
*
|
|
505
|
+
* const original = { a: 1, b: { c: 2 } };
|
|
506
|
+
* const cloned = deepClone(original);
|
|
507
|
+
* console.log(cloned); // { a: 1, b: { c: 2 } }
|
|
508
|
+
*
|
|
509
|
+
*/ const deepClone = (obj)=>{
|
|
510
|
+
if (obj === null || typeof obj !== 'object') {
|
|
511
|
+
return obj;
|
|
512
|
+
}
|
|
513
|
+
if (obj instanceof Date) {
|
|
514
|
+
return new Date(obj.getTime());
|
|
515
|
+
}
|
|
516
|
+
if (Array.isArray(obj)) {
|
|
517
|
+
return obj.map((item)=>deepClone(item));
|
|
518
|
+
}
|
|
519
|
+
const cloned = Object.keys(obj).reduce((acc, key)=>{
|
|
520
|
+
acc[key] = deepClone(obj[key]);
|
|
521
|
+
return acc;
|
|
522
|
+
}, {});
|
|
523
|
+
return cloned;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* 將嵌套物件的所有屬性設為指定的布林值
|
|
528
|
+
* @param obj - 目標物件
|
|
529
|
+
* @param boolValue - 布林值
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* const obj = { a: { b: 1, c: 2 }, d: 3 };
|
|
533
|
+
* const result = setBooleanToNestedObject(obj, true);
|
|
534
|
+
* console.log(result); // { a: { b: true, c: true }, d: true }
|
|
535
|
+
*/ const setBooleanToNestedObject = (obj, boolValue)=>Object.entries(obj).reduce((acc, [key, value])=>{
|
|
536
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
537
|
+
// 如果是嵌套物件,遞歸處理
|
|
538
|
+
acc[key] = setBooleanToNestedObject(value, boolValue);
|
|
539
|
+
} else {
|
|
540
|
+
// 如果是基本類型,設為布林值
|
|
541
|
+
acc[key] = boolValue;
|
|
542
|
+
}
|
|
543
|
+
return acc;
|
|
544
|
+
}, {});
|
|
545
|
+
/**
|
|
546
|
+
* 深度合併配置物件的通用工具
|
|
547
|
+
*
|
|
548
|
+
* @param defaultConfig - 預設配置
|
|
549
|
+
* @param customConfig - 自訂配置
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* const defaultConfig = { a: true, b: { b1: true, b2: true, b3: false }, c: true, d: false };
|
|
553
|
+
* mergeConfig(defaultConfig, { b: { b1: false }, d: true }); // { a: true, b: { b1: false, b2: true, b3: false }, c: true, d: true }
|
|
554
|
+
* mergeConfig(defaultConfig, { b: false }); // { a: true, b: { b1: false, b2: false, b3: false }, c: true, d: false }
|
|
555
|
+
* mergeConfig(defaultConfig, { b: true }); // { a: true, b: { b1: true, b2: true, b3: true }, c: true, d: false }
|
|
556
|
+
*/ const mergeConfig = (defaultConfig, customConfig = {})=>{
|
|
557
|
+
// 深拷貝預設配置以避免修改原始物件
|
|
558
|
+
const result = deepClone(defaultConfig);
|
|
559
|
+
return Object.entries(customConfig).reduce((acc, [key, sourceValue])=>{
|
|
560
|
+
const targetValue = acc[key];
|
|
561
|
+
// 如果來源值為 null 或 undefined,跳過
|
|
562
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
563
|
+
return acc;
|
|
564
|
+
}
|
|
565
|
+
// 如果目標物件中對應的值是物件,且來源值是布林值
|
|
566
|
+
// 這是特殊情況:用布林值覆蓋整個嵌套物件
|
|
567
|
+
if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'boolean') {
|
|
568
|
+
// 將嵌套物件的所有屬性設為該布林值
|
|
569
|
+
acc[key] = setBooleanToNestedObject(targetValue, sourceValue);
|
|
570
|
+
} else if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
571
|
+
acc[key] = mergeConfig(targetValue, sourceValue);
|
|
572
|
+
} else {
|
|
573
|
+
acc[key] = sourceValue;
|
|
574
|
+
}
|
|
575
|
+
return acc;
|
|
576
|
+
}, result);
|
|
577
|
+
};
|
|
503
578
|
|
|
504
579
|
/**
|
|
505
580
|
* debounce function
|
|
@@ -801,7 +876,7 @@ const transformObjectKey = (obj, transformFunName)=>{
|
|
|
801
876
|
*
|
|
802
877
|
* formatAmount(1234567) // '1,234,567'
|
|
803
878
|
*/ const formatAmount = (num)=>{
|
|
804
|
-
if (typeof num !== 'number') return '';
|
|
879
|
+
if (typeof num !== 'number' || isNaN(num)) return '';
|
|
805
880
|
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
|
806
881
|
};
|
|
807
882
|
/**
|
|
@@ -909,8 +984,7 @@ const FILE_SIZE_UNITS = [
|
|
|
909
984
|
*
|
|
910
985
|
* isEmail().test('123') // false
|
|
911
986
|
* isEmail().test('123@gmail.com') // true
|
|
912
|
-
*/ const isEmail = ()
|
|
913
|
-
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
|
|
987
|
+
*/ const isEmail = ()=>/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\\-]*\.)+[A-Z]{2,}$/i;
|
|
914
988
|
/**
|
|
915
989
|
* 日期字串
|
|
916
990
|
*
|
|
@@ -1109,6 +1183,29 @@ const FILE_SIZE_UNITS = [
|
|
|
1109
1183
|
// 再用 dayjs 驗證是否為有效日期
|
|
1110
1184
|
return dayjs(dateString, format, true).isValid();
|
|
1111
1185
|
};
|
|
1186
|
+
/**
|
|
1187
|
+
* 判斷 `value` 是否為 `null` 或 `undefined`
|
|
1188
|
+
*
|
|
1189
|
+
* @param value 值
|
|
1190
|
+
*
|
|
1191
|
+
* @example
|
|
1192
|
+
* isNil(null); // true
|
|
1193
|
+
* isNil(undefined); // true
|
|
1194
|
+
* isNil(0); // false
|
|
1195
|
+
* isNil(''); // false
|
|
1196
|
+
* isNil(false); // false
|
|
1197
|
+
* isNil({}); // false
|
|
1198
|
+
*
|
|
1199
|
+
* // TypeScript 型別縮窄應用
|
|
1200
|
+
* function example(input?: string | null) {
|
|
1201
|
+
* if (isNil(input)) {
|
|
1202
|
+
* // input 型別會自動縮窄成 null | undefined
|
|
1203
|
+
* return 'No value';
|
|
1204
|
+
* }
|
|
1205
|
+
* // input 型別自動為 string
|
|
1206
|
+
* return input.toUpperCase();
|
|
1207
|
+
* }
|
|
1208
|
+
*/ const isNil = (value)=>value === null || value === undefined;
|
|
1112
1209
|
|
|
1113
1210
|
/**
|
|
1114
1211
|
* Creates a strongly-typed React Context and Provider pair for data sharing.
|
|
@@ -1118,7 +1215,7 @@ const FILE_SIZE_UNITS = [
|
|
|
1118
1215
|
* and a Provider component for supplying context values.
|
|
1119
1216
|
*
|
|
1120
1217
|
* @template T - The value type for the context.
|
|
1121
|
-
* @returns {
|
|
1218
|
+
* @returns {object} An object containing:
|
|
1122
1219
|
* - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
|
|
1123
1220
|
* - DataProvider: A Provider component to wrap your component tree and supply the context value.
|
|
1124
1221
|
*
|
|
@@ -1285,4 +1382,4 @@ function mergeRefs(refs) {
|
|
|
1285
1382
|
return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
|
|
1286
1383
|
};
|
|
1287
1384
|
|
|
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 };
|
|
1385
|
+
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.1",
|
|
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",
|