@gateweb/react-utils 1.16.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -1,17 +1,93 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var dayjs = require('dayjs');
4
- var queryStore12s = require('./queryStore-12s-q_SLGgYH.js');
5
- var React = require('react');
6
- var useCountdown12s = require('./useCountdown-12s-uiqhgllY.js');
7
- var useDisclosure12s = require('./useDisclosure-12s-SZtbSE4A.js');
8
- var download12s = require('./download-12s-DKxkL92w.js');
9
- var webStorage12s = require('./webStorage-12s-DHr9PcPl.js');
10
4
 
11
5
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
6
 
13
7
  var dayjs__default = /*#__PURE__*/_interopDefault(dayjs);
14
- var React__default = /*#__PURE__*/_interopDefault(React);
8
+
9
+ /* eslint-disable no-bitwise */ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
10
+ /**
11
+ * 將位元組陣列編碼為 base64 字串
12
+ *
13
+ * @param bytes 要編碼的位元組陣列
14
+ * @returns base64 編碼的字串
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const bytes = new TextEncoder().encode('Hello World');
19
+ * const encoded = encodeBase64(bytes);
20
+ * console.log(encoded); // 'SGVsbG8gV29ybGQ='
21
+ * ```
22
+ */ const encodeBase64 = (bytes)=>{
23
+ let out = '';
24
+ for(let i = 0; i < bytes.length; i += 3){
25
+ const n = bytes[i] << 16 | (bytes[i + 1] || 0) << 8 | (bytes[i + 2] || 0);
26
+ out += chars[n >> 18 & 63] + chars[n >> 12 & 63] + (i + 1 < bytes.length ? chars[n >> 6 & 63] : '=') + (i + 2 < bytes.length ? chars[n & 63] : '=');
27
+ }
28
+ return out;
29
+ };
30
+ /**
31
+ * 將 base64 字串解碼為位元組陣列
32
+ *
33
+ * @param base64 要解碼的 base64 字串(支援 base64url 格式)
34
+ * @returns 解碼後的位元組陣列
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const decoded = decodeBase64('SGVsbG8gV29ybGQ=');
39
+ * const text = new TextDecoder().decode(decoded);
40
+ * console.log(text); // 'Hello World'
41
+ * ```
42
+ */ const decodeBase64 = (base64)=>{
43
+ let out = base64.replace(/-/g, '+').replace(/_/g, '/'); // 支援 base64url
44
+ while(out.length % 4)out += '='; // 自動補 "="
45
+ const buffer = [];
46
+ for(let i = 0; i < out.length; i += 4){
47
+ const n = chars.indexOf(out[i]) << 18 | chars.indexOf(out[i + 1]) << 12 | (chars.indexOf(out[i + 2]) & 63) << 6 | chars.indexOf(out[i + 3]) & 63;
48
+ buffer.push(n >> 16 & 255);
49
+ if (out[i + 2] !== '=') buffer.push(n >> 8 & 255);
50
+ if (out[i + 3] !== '=') buffer.push(n & 255);
51
+ }
52
+ return new Uint8Array(buffer);
53
+ };
54
+ /**
55
+ * 將 JSON 資料編碼為 base64 字串
56
+ *
57
+ * @param data 要編碼的 JSON 可序列化資料
58
+ * @returns base64 編碼的字串
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const data = { name: 'John', age: 30 };
63
+ * const encoded = encodeJson(data);
64
+ * console.log(encoded); // 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9'
65
+ *
66
+ * const array = [1, 2, 3];
67
+ * const encodedArray = encodeJson(array);
68
+ * ```
69
+ */ const encodeJson = (data)=>{
70
+ const json = JSON.stringify(data);
71
+ return encodeBase64(new TextEncoder().encode(json));
72
+ };
73
+ /**
74
+ * 將 base64 字串解碼為 JSON 資料
75
+ *
76
+ * @param b64 要解碼的 base64 字串
77
+ * @returns 解碼後的資料
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const encoded = 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9';
82
+ * const decoded = decodeJson<{ name: string; age: number }>(encoded);
83
+ * console.log(decoded); // { name: 'John', age: 30 }
84
+ *
85
+ * const decodedArray = decodeJson<number[]>(encodedArray);
86
+ * ```
87
+ */ const decodeJson = (b64)=>{
88
+ const json = new TextDecoder().decode(decodeBase64(b64));
89
+ return JSON.parse(json);
90
+ };
15
91
 
16
92
  const FILE_SIZE_UNITS$1 = [
17
93
  'Bytes',
@@ -1246,100 +1322,6 @@ const FILE_SIZE_UNITS = [
1246
1322
  * }
1247
1323
  */ const isNil = (value)=>value === null || value === undefined;
1248
1324
 
1249
- /**
1250
- * Creates a strongly-typed React Context and Provider pair for data sharing.
1251
- *
1252
- * This utility helps you avoid prop drilling by providing a reusable way to define
1253
- * context with strict type inference. It returns a custom hook for consuming the context
1254
- * and a Provider component for supplying context values.
1255
- *
1256
- * @template T - The value type for the context.
1257
- * @returns {object} An object containing:
1258
- * - useDataContext: A custom hook to access the context value. Throws an error if used outside the provider.
1259
- * - DataProvider: A Provider component to wrap your component tree and supply the context value.
1260
- *
1261
- * @example
1262
- * // Example usage:
1263
- * const { useDataContext, DataProvider } = createDataContext<{ count: number }>();
1264
- *
1265
- * function Counter() {
1266
- * const { count } = useDataContext();
1267
- * return <span>{count}</span>;
1268
- * }
1269
- *
1270
- * function App() {
1271
- * return (
1272
- * <DataProvider value={{ count: 42 }}>
1273
- * <Counter />
1274
- * </DataProvider>
1275
- * );
1276
- * }
1277
- */ const createDataContext = ()=>{
1278
- const Context = /*#__PURE__*/ React.createContext(undefined);
1279
- const useDataContext = ()=>{
1280
- const context = React.useContext(Context);
1281
- if (context === undefined) {
1282
- throw new Error(`useDataContext must be used within a DataProvider`);
1283
- }
1284
- return context;
1285
- };
1286
- const DataProvider = ({ children, value })=>{
1287
- const memoized = React.useMemo(()=>value, [
1288
- value
1289
- ]);
1290
- return /*#__PURE__*/ React__default.default.createElement(Context.Provider, {
1291
- value: memoized
1292
- }, children);
1293
- };
1294
- return {
1295
- useDataContext,
1296
- DataProvider
1297
- };
1298
- };
1299
-
1300
- /**
1301
- * A hook to manage a value.
1302
- *
1303
- * @example
1304
- *
1305
- * ```tsx
1306
- * const MyComponent = ({ value }: { value?: number }) => {
1307
- * const [currentValue, setCurrentValue] = useValue({ value });
1308
- * };
1309
- * ```
1310
- */ const useValue = ({ value, defaultValue })=>{
1311
- if (value === undefined && defaultValue === undefined) {
1312
- throw new Error('Either `value` or `defaultValue` must be provided.');
1313
- }
1314
- const isControlled = value !== undefined;
1315
- const [internalValue, setInternalValue] = React.useState(defaultValue);
1316
- const setValue = React.useCallback((newValue)=>{
1317
- if (!isControlled) {
1318
- setInternalValue(newValue);
1319
- }
1320
- }, [
1321
- isControlled
1322
- ]);
1323
- const currentValue = isControlled ? value : internalValue;
1324
- return [
1325
- currentValue,
1326
- setValue
1327
- ];
1328
- };
1329
-
1330
- function mergeRefs(refs) {
1331
- return (value)=>{
1332
- refs.forEach((ref)=>{
1333
- if (typeof ref === 'function') {
1334
- ref(value);
1335
- } else if (ref != null) {
1336
- // eslint-disable-next-line no-param-reassign
1337
- ref.current = value;
1338
- }
1339
- });
1340
- };
1341
- }
1342
-
1343
1325
  /**
1344
1326
  * 民國年轉西元年
1345
1327
  * @param dateString 日期字串
@@ -1421,13 +1403,6 @@ function mergeRefs(refs) {
1421
1403
  return dayjs__default.default(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
1422
1404
  };
1423
1405
 
1424
- exports.QueryProvider = queryStore12s.QueryProvider;
1425
- exports.useQueryContext = queryStore12s.useQueryContext;
1426
- exports.useCountdown = useCountdown12s.useCountdown;
1427
- exports.useDisclosure = useDisclosure12s.useDisclosure;
1428
- exports.downloadFile = download12s.downloadFile;
1429
- exports.getLocalStorage = webStorage12s.getLocalStorage;
1430
- exports.setLocalStorage = webStorage12s.setLocalStorage;
1431
1406
  exports.ByteSize = ByteSize;
1432
1407
  exports.MimeTypeMap = MimeTypeMap;
1433
1408
  exports.OtherMimeType = OtherMimeType;
@@ -1437,11 +1412,14 @@ exports.camelCase2SnakeCase = camelCase2SnakeCase;
1437
1412
  exports.camelString2PascalString = camelString2PascalString;
1438
1413
  exports.camelString2SnakeString = camelString2SnakeString;
1439
1414
  exports.convertBytes = convertBytes;
1440
- exports.createDataContext = createDataContext;
1441
1415
  exports.createEnumLikeObject = createEnumLikeObject;
1442
1416
  exports.debounce = debounce;
1417
+ exports.decodeBase64 = decodeBase64;
1418
+ exports.decodeJson = decodeJson;
1443
1419
  exports.deepClone = deepClone;
1444
1420
  exports.deepMerge = deepMerge;
1421
+ exports.encodeBase64 = encodeBase64;
1422
+ exports.encodeJson = encodeJson;
1445
1423
  exports.extractEnumLikeObject = extractEnumLikeObject;
1446
1424
  exports.fakeApi = fakeApi;
1447
1425
  exports.formatAmount = formatAmount;
@@ -1470,7 +1448,6 @@ exports.isTimeString = isTimeString;
1470
1448
  exports.isValidPassword = isValidPassword;
1471
1449
  exports.maskString = maskString;
1472
1450
  exports.mergeConfig = mergeConfig;
1473
- exports.mergeRefs = mergeRefs;
1474
1451
  exports.objectToSearchParams = objectToSearchParams;
1475
1452
  exports.omit = omit;
1476
1453
  exports.omitByValue = omitByValue;
@@ -1490,7 +1467,6 @@ exports.snakeCase2PascalCase = snakeCase2PascalCase;
1490
1467
  exports.snakeString2CamelString = snakeString2CamelString;
1491
1468
  exports.snakeString2PascalString = snakeString2PascalString;
1492
1469
  exports.throttle = throttle;
1493
- exports.useValue = useValue;
1494
1470
  exports.validTaxId = validTaxId;
1495
1471
  exports.validateDateString = validateDateString;
1496
1472
  exports.validateFileType = validateFileType;
@@ -30,6 +30,8 @@ const createQueryStore = ({ query, handleChangeQuery })=>zustand.createStore()((
30
30
  const QueryContext = /*#__PURE__*/ React.createContext(null);
31
31
  /**
32
32
  * Provider to provide the store to the context
33
+ *
34
+ * @deprecated use `StoreProvider` from `core/store` instead
33
35
  */ const QueryProvider = ({ children, query, handleChangeQuery })=>{
34
36
  const storeRef = React.useRef(null);
35
37
  if (!storeRef.current) {
@@ -61,6 +63,8 @@ const QueryContext = /*#__PURE__*/ React.createContext(null);
61
63
  * const result2 = useQueryContext<MyObject>()(q => q.changeQuery);
62
64
  * const result3 = useQueryContext<MyObject>()(q => q.query);
63
65
  * ```
66
+ *
67
+ * @deprecated use `useStoreContext` from `core/store` instead
64
68
  */ const useQueryContext = ()=>{
65
69
  const store = React.useContext(QueryContext);
66
70
  if (!store) throw new Error('Missing QueryContext.Provider in the tree');
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+ var React = require('react');
3
+ var zustand = require('zustand');
4
+ var traditional = require('zustand/traditional');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var React__default = /*#__PURE__*/_interopDefault(React);
9
+
10
+ const isObject = (value)=>value !== null && typeof value === 'object';
11
+ /**
12
+ * merge two objects deeply
13
+ *
14
+ * @param target - the target object
15
+ * @param source - the source object
16
+ *
17
+ * @example
18
+ *
19
+ * const obja = { a: { a1: { a11: 'value 1', a12: 'value 2' }, a2: 'value 3' }, b: 'value 4' };
20
+ * const objb = { a: { a1: { a13: 'value 5', a14: 'value 6' }, a3: 'value 7' }};
21
+ *
22
+ * const mergeResult = deepMerge(obja, objb); // { a: { a1: { a11: 'value 1', a12: 'value 2', a13: 'value 5', a14: 'value 6' }, a2: 'value 3', a3: 'value 7' }, b: 'value 4' }
23
+ *
24
+ */ const deepMerge = (target, source)=>Object.entries(source).reduce((acc, [key, value])=>{
25
+ if (isObject(value)) {
26
+ acc[key] = key in target && isObject(target[key]) ? deepMerge(target[key], value) : value;
27
+ } else {
28
+ acc[key] = value;
29
+ }
30
+ return acc;
31
+ }, {
32
+ ...target
33
+ });
34
+
35
+ /**
36
+ * create a zustand store with changeStore method
37
+ */ const createZustandStore = ({ store, handleChangeStore, defaultMerge })=>zustand.createStore()((set)=>({
38
+ store,
39
+ changeStore: (updater, options)=>{
40
+ set((pre)=>{
41
+ const patch = typeof updater === 'function' ? updater(pre.store) : updater;
42
+ const newStore = handleChangeStore ? handleChangeStore(pre.store, patch) : patch;
43
+ const strategy = options?.merge ?? defaultMerge;
44
+ if (strategy === 'shallow') {
45
+ return {
46
+ store: {
47
+ ...pre.store,
48
+ ...newStore
49
+ }
50
+ };
51
+ }
52
+ return {
53
+ store: deepMerge(pre.store, newStore)
54
+ };
55
+ });
56
+ }
57
+ }));
58
+ /**
59
+ * create a store context with provider and hooks
60
+ *
61
+ * @example
62
+ *
63
+ * ```tsx
64
+ * // create a store context
65
+ * const { StoreProvider, useStore, useStoreApi } = createStoreContext<MyObject>();
66
+ *
67
+ * // use the StoreProvider to provide the store to the context
68
+ * <StoreProvider store={{ key: 'value' }}>
69
+ * <App />
70
+ * </StoreProvider>
71
+ * ```
72
+ */ const createStoreContext = ()=>{
73
+ const StoreContext = /*#__PURE__*/ React.createContext(null);
74
+ /**
75
+ * Provider to provide the store to the context
76
+ *
77
+ * @example
78
+ *
79
+ * ```tsx
80
+ * // use the StoreProvider to provide the store to the context
81
+ * const { StoreProvider } = createStoreContext<MyObject>();
82
+ *
83
+ * <StoreProvider store={{ key: 'value' }}>
84
+ * <App />
85
+ * </StoreProvider>
86
+ * ```
87
+ */ const StoreProvider = ({ children, store = {}, handleChangeStore, defaultMerge = 'shallow' })=>{
88
+ const storeRef = React.useRef(null);
89
+ if (!storeRef.current) {
90
+ storeRef.current = createZustandStore({
91
+ store,
92
+ handleChangeStore,
93
+ defaultMerge
94
+ });
95
+ }
96
+ return /*#__PURE__*/ React__default.default.createElement(StoreContext.Provider, {
97
+ value: storeRef.current
98
+ }, children);
99
+ };
100
+ /**
101
+ * a hook to get the store from the context
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * const { useStore } = createStoreContext<MyObject>();
106
+ * const value = useStore(s => s.store);
107
+ * ```
108
+ *
109
+ * @param selector - the selector to get the state from the store
110
+ * @param equalityFn - the equality function to compare the previous and next state
111
+ */ const useStore = (selector, equalityFn)=>{
112
+ const store = React.useContext(StoreContext);
113
+ if (!store) throw new Error('Missing StoreContext.Provider in the tree');
114
+ return traditional.useStoreWithEqualityFn(store, selector, equalityFn);
115
+ };
116
+ return {
117
+ StoreProvider,
118
+ useStore
119
+ };
120
+ };
121
+
122
+ exports.createStoreContext = createStoreContext;
@@ -0,0 +1,136 @@
1
+ 'use client';
2
+ /* eslint-disable no-bitwise */ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
3
+ /**
4
+ * 將位元組陣列編碼為 base64 字串
5
+ *
6
+ * @param bytes 要編碼的位元組陣列
7
+ * @returns base64 編碼的字串
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const bytes = new TextEncoder().encode('Hello World');
12
+ * const encoded = encodeBase64(bytes);
13
+ * console.log(encoded); // 'SGVsbG8gV29ybGQ='
14
+ * ```
15
+ */ const encodeBase64 = (bytes)=>{
16
+ let out = '';
17
+ for(let i = 0; i < bytes.length; i += 3){
18
+ const n = bytes[i] << 16 | (bytes[i + 1] || 0) << 8 | (bytes[i + 2] || 0);
19
+ out += chars[n >> 18 & 63] + chars[n >> 12 & 63] + (i + 1 < bytes.length ? chars[n >> 6 & 63] : '=') + (i + 2 < bytes.length ? chars[n & 63] : '=');
20
+ }
21
+ return out;
22
+ };
23
+ /**
24
+ * 將 base64 字串解碼為位元組陣列
25
+ *
26
+ * @param base64 要解碼的 base64 字串(支援 base64url 格式)
27
+ * @returns 解碼後的位元組陣列
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const decoded = decodeBase64('SGVsbG8gV29ybGQ=');
32
+ * const text = new TextDecoder().decode(decoded);
33
+ * console.log(text); // 'Hello World'
34
+ * ```
35
+ */ const decodeBase64 = (base64)=>{
36
+ let out = base64.replace(/-/g, '+').replace(/_/g, '/'); // 支援 base64url
37
+ while(out.length % 4)out += '='; // 自動補 "="
38
+ const buffer = [];
39
+ for(let i = 0; i < out.length; i += 4){
40
+ const n = chars.indexOf(out[i]) << 18 | chars.indexOf(out[i + 1]) << 12 | (chars.indexOf(out[i + 2]) & 63) << 6 | chars.indexOf(out[i + 3]) & 63;
41
+ buffer.push(n >> 16 & 255);
42
+ if (out[i + 2] !== '=') buffer.push(n >> 8 & 255);
43
+ if (out[i + 3] !== '=') buffer.push(n & 255);
44
+ }
45
+ return new Uint8Array(buffer);
46
+ };
47
+ /**
48
+ * 將 JSON 資料編碼為 base64 字串
49
+ *
50
+ * @param data 要編碼的 JSON 可序列化資料
51
+ * @returns base64 編碼的字串
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const data = { name: 'John', age: 30 };
56
+ * const encoded = encodeJson(data);
57
+ * console.log(encoded); // 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9'
58
+ *
59
+ * const array = [1, 2, 3];
60
+ * const encodedArray = encodeJson(array);
61
+ * ```
62
+ */ const encodeJson = (data)=>{
63
+ const json = JSON.stringify(data);
64
+ return encodeBase64(new TextEncoder().encode(json));
65
+ };
66
+ /**
67
+ * 將 base64 字串解碼為 JSON 資料
68
+ *
69
+ * @param b64 要解碼的 base64 字串
70
+ * @returns 解碼後的資料
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const encoded = 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9';
75
+ * const decoded = decodeJson<{ name: string; age: number }>(encoded);
76
+ * console.log(decoded); // { name: 'John', age: 30 }
77
+ *
78
+ * const decodedArray = decodeJson<number[]>(encodedArray);
79
+ * ```
80
+ */ const decodeJson = (b64)=>{
81
+ const json = new TextDecoder().decode(decodeBase64(b64));
82
+ return JSON.parse(json);
83
+ };
84
+
85
+ /**
86
+ * 從 localStorage 取得資料,支援槽狀取值(Json 物件)
87
+ *
88
+ * @param key 鍵值
89
+ * @param deCode 是否解碼
90
+ * @returns 取得的資料
91
+ * @example
92
+ * const data = getLocalStorage('key');
93
+ *
94
+ * const data = getLocalStorage('key.subKey');
95
+ */ const getLocalStorage = (key, deCode = true)=>{
96
+ const keys = key.split('.');
97
+ const storage = localStorage.getItem(keys[0]);
98
+ if (!storage) return undefined;
99
+ let currentObject;
100
+ try {
101
+ if (deCode) {
102
+ currentObject = decodeJson(storage);
103
+ } else {
104
+ currentObject = JSON.parse(storage);
105
+ }
106
+ } catch (_error) {
107
+ return undefined;
108
+ }
109
+ // let currentObject = JSON.parse(storage);
110
+ if (keys.length === 1) {
111
+ return currentObject;
112
+ }
113
+ keys.shift();
114
+ while(keys.length > 0){
115
+ const currentKey = keys.shift();
116
+ if (!currentKey) break;
117
+ currentObject = currentObject[currentKey];
118
+ if (!currentObject) break;
119
+ }
120
+ return currentObject;
121
+ };
122
+ /**
123
+ * 將資料(Json 物件)存入 localStorage
124
+ * @param key 鍵值
125
+ * @param value 可序列化的資料
126
+ * @param enCode 是否編碼
127
+ */ const setLocalStorage = (key, value, enCode = true)=>{
128
+ if (enCode) {
129
+ localStorage.setItem(key, encodeJson(value));
130
+ } else {
131
+ localStorage.setItem(key, JSON.stringify(value));
132
+ }
133
+ };
134
+
135
+ exports.getLocalStorage = getLocalStorage;
136
+ exports.setLocalStorage = setLocalStorage;