@gateweb/react-utils 2.0.0 → 2.2.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/client.d.ts +28 -6
- package/dist/cjs/client.js +2 -0
- package/dist/cjs/index.d.ts +31 -2
- package/dist/cjs/index.js +69 -0
- package/dist/cjs/types.d.ts +5 -3
- package/dist/cjs/useHorizontalWheel-12s-bwbWVJeM.js +47 -0
- package/dist/es/client.d.mts +28 -6
- package/dist/es/client.mjs +1 -0
- package/dist/es/index.d.mts +31 -2
- package/dist/es/index.mjs +69 -1
- package/dist/es/types.d.mts +5 -3
- package/dist/es/useHorizontalWheel-12s-D3IfutV9.mjs +47 -0
- package/package.json +1 -1
package/dist/cjs/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { ReactNode } from 'react';
|
|
2
3
|
import { AtLeastOne } from './types.js';
|
|
3
4
|
|
|
4
5
|
type TStoreProps<S> = {
|
|
@@ -88,7 +89,7 @@ type TInitialProps$1<S> = TStoreProps<S> & {
|
|
|
88
89
|
* ```
|
|
89
90
|
*/
|
|
90
91
|
declare const createStoreContext: <S>() => {
|
|
91
|
-
StoreProvider: ({ children, store, handleChangeStore, defaultMerge, }:
|
|
92
|
+
StoreProvider: ({ children, store, handleChangeStore, defaultMerge, }: React__default.PropsWithChildren<Partial<TInitialProps$1<S>>>) => React__default.JSX.Element;
|
|
92
93
|
useStore: <T>(selector: (state: TStoreState<S>) => T, equalityFn?: (left: T, right: T) => boolean) => T;
|
|
93
94
|
};
|
|
94
95
|
|
|
@@ -121,7 +122,7 @@ type TInitialProps<Q> = Partial<TQueryProps<Q> & {
|
|
|
121
122
|
*
|
|
122
123
|
* @deprecated use `StoreProvider` from `core/store` instead
|
|
123
124
|
*/
|
|
124
|
-
declare const QueryProvider: <Q>({ children, query, handleChangeQuery, }:
|
|
125
|
+
declare const QueryProvider: <Q>({ children, query, handleChangeQuery, }: React__default.PropsWithChildren<TInitialProps<Q>>) => React__default.JSX.Element;
|
|
125
126
|
/**
|
|
126
127
|
* hook to get the store from the context
|
|
127
128
|
*
|
|
@@ -180,7 +181,7 @@ declare const createDataContext: <T>() => {
|
|
|
180
181
|
readonly DataProvider: ({ children, value }: {
|
|
181
182
|
children: ReactNode;
|
|
182
183
|
value: T;
|
|
183
|
-
}) =>
|
|
184
|
+
}) => React__default.JSX.Element;
|
|
184
185
|
};
|
|
185
186
|
|
|
186
187
|
type TCountdownActions = {
|
|
@@ -227,6 +228,27 @@ type UseDisclosureReturn = {
|
|
|
227
228
|
*/
|
|
228
229
|
declare function useDisclosure(initialState?: boolean): UseDisclosureReturn;
|
|
229
230
|
|
|
231
|
+
type Options = {
|
|
232
|
+
/** 滾輪轉成水平位移的倍率(觸控板通常較小,滑鼠滾輪較大) */
|
|
233
|
+
speed?: number;
|
|
234
|
+
/** 是否在按住 Shift 時改走瀏覽器原生(通常 Shift+滾輪就是橫向) */
|
|
235
|
+
respectShiftKey?: boolean;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* 將 element 的垂直滾輪事件轉成水平捲動的 hook
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
*
|
|
242
|
+
* ```tsx
|
|
243
|
+
* const ref = useHorizontalWheel();
|
|
244
|
+
*
|
|
245
|
+
* return <div ref={ref}>...</div>;
|
|
246
|
+
*
|
|
247
|
+
* // 你將可以用垂直滾輪來水平捲動這個 div 了!
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
declare const useHorizontalWheel: <T extends HTMLElement>({ speed, respectShiftKey, }?: Options) => React.RefObject<T | null>;
|
|
251
|
+
|
|
230
252
|
type TValueOptions<T> = AtLeastOne<{
|
|
231
253
|
/**
|
|
232
254
|
* The controlled value.
|
|
@@ -250,7 +272,7 @@ type TValueOptions<T> = AtLeastOne<{
|
|
|
250
272
|
*/
|
|
251
273
|
declare const useValue: <T>({ value, defaultValue }: TValueOptions<T>) => readonly [T, (newValue: T) => void];
|
|
252
274
|
|
|
253
|
-
declare function mergeRefs<T = any>(refs: Array<
|
|
275
|
+
declare function mergeRefs<T = any>(refs: Array<React__default.MutableRefObject<T> | React__default.LegacyRef<T> | undefined | null>): React__default.RefCallback<T>;
|
|
254
276
|
|
|
255
277
|
/**
|
|
256
278
|
* Downloads a file from a given source.
|
|
@@ -285,5 +307,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
|
|
|
285
307
|
*/
|
|
286
308
|
declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
|
|
287
309
|
|
|
288
|
-
export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useQueryContext, useValue };
|
|
310
|
+
export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
|
|
289
311
|
export type { TCountdownActions, UseDisclosureReturn };
|
package/dist/cjs/client.js
CHANGED
|
@@ -5,6 +5,7 @@ var queryStore12s = require('./queryStore-12s-JzzXvjJC.js');
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var useCountdown12s = require('./useCountdown-12s-uiqhgllY.js');
|
|
7
7
|
var useDisclosure12s = require('./useDisclosure-12s-SZtbSE4A.js');
|
|
8
|
+
var useHorizontalWheel12s = require('./useHorizontalWheel-12s-bwbWVJeM.js');
|
|
8
9
|
var download12s = require('./download-12s-DKxkL92w.js');
|
|
9
10
|
var webStorage12s = require('./webStorage-12s-0RtNO_uc.js');
|
|
10
11
|
|
|
@@ -111,6 +112,7 @@ exports.QueryProvider = queryStore12s.QueryProvider;
|
|
|
111
112
|
exports.useQueryContext = queryStore12s.useQueryContext;
|
|
112
113
|
exports.useCountdown = useCountdown12s.useCountdown;
|
|
113
114
|
exports.useDisclosure = useDisclosure12s.useDisclosure;
|
|
115
|
+
exports.useHorizontalWheel = useHorizontalWheel12s.useHorizontalWheel;
|
|
114
116
|
exports.downloadFile = download12s.downloadFile;
|
|
115
117
|
exports.getLocalStorage = webStorage12s.getLocalStorage;
|
|
116
118
|
exports.setLocalStorage = webStorage12s.setLocalStorage;
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -706,6 +706,35 @@ declare function invariant(condition: any, message?: string | (() => string)): a
|
|
|
706
706
|
*/
|
|
707
707
|
declare const isServer: () => boolean;
|
|
708
708
|
|
|
709
|
+
type MaskKeySelector = string | ((key: string, value: unknown, path: Array<string | number>) => boolean);
|
|
710
|
+
/**
|
|
711
|
+
* 深度走訪物件/陣列,將指定鍵名且值為字串的欄位遮罩為固定字串(預設 "******")。
|
|
712
|
+
*
|
|
713
|
+
* - 預設僅遮罩鍵名等於 `password` 的欄位(區分大小寫)
|
|
714
|
+
* - 可傳入「字串鍵名」或「函式選擇器」來自訂要遮罩的欄位
|
|
715
|
+
* - 僅在值為字串時遮罩,其他型別維持原樣
|
|
716
|
+
* - 不變更輸入參考;回傳全新的資料結構
|
|
717
|
+
* - 函式選擇器會收到 `key`、`value` 與 `path`(到該鍵的路徑,陣列用索引)
|
|
718
|
+
* - 支援巢狀物件與陣列
|
|
719
|
+
*
|
|
720
|
+
* @param input - 來源資料
|
|
721
|
+
* @param mask - 遮罩字串,預設為 "******"
|
|
722
|
+
* @param selector - 欄位選擇器,字串或函式,預設 'password'
|
|
723
|
+
* @returns 回傳遮罩後的新資料
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* const input = { password: 'secret', user: { password: 'abc' }, list: [{ password: 'x' }, { ok: 1 }] };
|
|
727
|
+
* const output = maskPasswords(input);
|
|
728
|
+
* // { password: '******', user: { password: '******' }, list: [{ password: '******' }, { ok: 1 }] }
|
|
729
|
+
*
|
|
730
|
+
* // 指定鍵名
|
|
731
|
+
* maskPasswords({ secret: 'xxx' }, '***', 'secret'); // { secret: '***' }
|
|
732
|
+
*
|
|
733
|
+
* // 使用函式選擇器(鍵名包含 'pass' 都遮)
|
|
734
|
+
* maskPasswords({ password: 'a', passcode: 'b' }, '***', (k) => k.includes('pass')); // { password: '***', passcode: '***' }
|
|
735
|
+
*/
|
|
736
|
+
declare const maskPasswords: <T>(input: T, mask?: string, selector?: MaskKeySelector) => T;
|
|
737
|
+
|
|
709
738
|
type DeepPartialWithBooleanOverride<T> = {
|
|
710
739
|
[K in keyof T]?: T[K] extends object ? T[K] extends boolean ? T[K] : DeepPartialWithBooleanOverride<T[K]> | boolean : T[K];
|
|
711
740
|
};
|
|
@@ -1232,5 +1261,5 @@ declare const generatePeriodArray: () => string[];
|
|
|
1232
1261
|
*/
|
|
1233
1262
|
declare const getCurrentPeriod: () => string;
|
|
1234
1263
|
|
|
1235
|
-
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
|
1236
|
-
export type { MimeTypeExtension, MimeTypeValue, PartialBy, RequiredBy };
|
|
1264
|
+
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, maskPasswords, maskString, mergeConfig, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
|
1265
|
+
export type { MaskKeySelector, MimeTypeExtension, MimeTypeValue, PartialBy, RequiredBy };
|
package/dist/cjs/index.js
CHANGED
|
@@ -493,6 +493,74 @@ message) {
|
|
|
493
493
|
* 判斷執行環境是否為 Server(node.js) 端
|
|
494
494
|
*/ const isServer = ()=>typeof window === 'undefined';
|
|
495
495
|
|
|
496
|
+
/**
|
|
497
|
+
* 深度走訪物件/陣列,將指定鍵名且值為字串的欄位遮罩為固定字串(預設 "******")。
|
|
498
|
+
*
|
|
499
|
+
* - 預設僅遮罩鍵名等於 `password` 的欄位(區分大小寫)
|
|
500
|
+
* - 可傳入「字串鍵名」或「函式選擇器」來自訂要遮罩的欄位
|
|
501
|
+
* - 僅在值為字串時遮罩,其他型別維持原樣
|
|
502
|
+
* - 不變更輸入參考;回傳全新的資料結構
|
|
503
|
+
* - 函式選擇器會收到 `key`、`value` 與 `path`(到該鍵的路徑,陣列用索引)
|
|
504
|
+
* - 支援巢狀物件與陣列
|
|
505
|
+
*
|
|
506
|
+
* @param input - 來源資料
|
|
507
|
+
* @param mask - 遮罩字串,預設為 "******"
|
|
508
|
+
* @param selector - 欄位選擇器,字串或函式,預設 'password'
|
|
509
|
+
* @returns 回傳遮罩後的新資料
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* const input = { password: 'secret', user: { password: 'abc' }, list: [{ password: 'x' }, { ok: 1 }] };
|
|
513
|
+
* const output = maskPasswords(input);
|
|
514
|
+
* // { password: '******', user: { password: '******' }, list: [{ password: '******' }, { ok: 1 }] }
|
|
515
|
+
*
|
|
516
|
+
* // 指定鍵名
|
|
517
|
+
* maskPasswords({ secret: 'xxx' }, '***', 'secret'); // { secret: '***' }
|
|
518
|
+
*
|
|
519
|
+
* // 使用函式選擇器(鍵名包含 'pass' 都遮)
|
|
520
|
+
* maskPasswords({ password: 'a', passcode: 'b' }, '***', (k) => k.includes('pass')); // { password: '***', passcode: '***' }
|
|
521
|
+
*/ const maskPasswords = (input, mask = '******', selector = 'password')=>{
|
|
522
|
+
const isObject = (val)=>val !== null && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date);
|
|
523
|
+
const shouldMask = (key, value, path)=>{
|
|
524
|
+
if (typeof selector === 'string') {
|
|
525
|
+
return key === selector && typeof value === 'string';
|
|
526
|
+
}
|
|
527
|
+
return selector(key, value, path) && typeof value === 'string';
|
|
528
|
+
};
|
|
529
|
+
const walk = (val, path)=>{
|
|
530
|
+
if (val === null || val === undefined) return val;
|
|
531
|
+
if (Array.isArray(val)) {
|
|
532
|
+
return val.map((item, idx)=>walk(item, [
|
|
533
|
+
...path,
|
|
534
|
+
idx
|
|
535
|
+
]));
|
|
536
|
+
}
|
|
537
|
+
if (val instanceof Date) {
|
|
538
|
+
return new Date(val.getTime());
|
|
539
|
+
}
|
|
540
|
+
if (isObject(val)) {
|
|
541
|
+
const result = Object.entries(val).reduce((acc, [key, v])=>{
|
|
542
|
+
const nextPath = [
|
|
543
|
+
...path,
|
|
544
|
+
key
|
|
545
|
+
];
|
|
546
|
+
if (shouldMask(key, v, nextPath)) {
|
|
547
|
+
return {
|
|
548
|
+
...acc,
|
|
549
|
+
[key]: mask
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
...acc,
|
|
554
|
+
[key]: walk(v, nextPath)
|
|
555
|
+
};
|
|
556
|
+
}, {});
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
return val;
|
|
560
|
+
};
|
|
561
|
+
return walk(input, []);
|
|
562
|
+
};
|
|
563
|
+
|
|
496
564
|
/**
|
|
497
565
|
* 將物件中的某些 key 排除
|
|
498
566
|
*
|
|
@@ -1446,6 +1514,7 @@ exports.isTWMobile = isTWMobile;
|
|
|
1446
1514
|
exports.isTWPhone = isTWPhone;
|
|
1447
1515
|
exports.isTimeString = isTimeString;
|
|
1448
1516
|
exports.isValidPassword = isValidPassword;
|
|
1517
|
+
exports.maskPasswords = maskPasswords;
|
|
1449
1518
|
exports.maskString = maskString;
|
|
1450
1519
|
exports.mergeConfig = mergeConfig;
|
|
1451
1520
|
exports.objectToSearchParams = objectToSearchParams;
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -137,6 +137,8 @@ type OnlyOne<T> = {
|
|
|
137
137
|
[P in Exclude<keyof T, K>]?: never;
|
|
138
138
|
};
|
|
139
139
|
}[keyof T];
|
|
140
|
+
type Builtin = string | number | boolean | bigint | symbol | null | undefined;
|
|
141
|
+
type NonRecursive = Builtin | Function | Date | RegExp | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any> | Promise<any>;
|
|
140
142
|
/**
|
|
141
143
|
* A utility type that makes all properties in the generic type `T` optional. Also, it makes all nested properties optional.
|
|
142
144
|
*
|
|
@@ -154,9 +156,9 @@ type OnlyOne<T> = {
|
|
|
154
156
|
*
|
|
155
157
|
* const obj2: ExampleDeepPartial = { a: { b: 'world' } }; // OK
|
|
156
158
|
*/
|
|
157
|
-
type DeepPartial<T> =
|
|
158
|
-
[
|
|
159
|
-
}
|
|
159
|
+
type DeepPartial<T> = T extends NonRecursive ? T : T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? {
|
|
160
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
161
|
+
} : T;
|
|
160
162
|
/**
|
|
161
163
|
* 將 Obj 指定的 key 轉換成指定的型別
|
|
162
164
|
*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var React = require('react');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 將 element 的垂直滾輪事件轉成水平捲動的 hook
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const ref = useHorizontalWheel();
|
|
11
|
+
*
|
|
12
|
+
* return <div ref={ref}>...</div>;
|
|
13
|
+
*
|
|
14
|
+
* // 你將可以用垂直滾輪來水平捲動這個 div 了!
|
|
15
|
+
* ```
|
|
16
|
+
*/ const useHorizontalWheel = ({ speed = 1, respectShiftKey = true } = {})=>{
|
|
17
|
+
const ref = React.useRef(null);
|
|
18
|
+
React.useEffect(()=>{
|
|
19
|
+
const el = ref.current;
|
|
20
|
+
if (!el) return undefined;
|
|
21
|
+
const onWheel = (e)=>{
|
|
22
|
+
if (respectShiftKey && e.shiftKey) return;
|
|
23
|
+
// deltaY 是「想往下滾」的意圖,我們把它轉成 scrollLeft
|
|
24
|
+
const delta = e.deltaY * speed;
|
|
25
|
+
const maxScrollLeft = el.scrollWidth - el.clientWidth;
|
|
26
|
+
const next = el.scrollLeft + delta;
|
|
27
|
+
const canScrollHorizontally = el.scrollWidth > el.clientWidth;
|
|
28
|
+
if (!canScrollHorizontally) return;
|
|
29
|
+
const willScroll = delta < 0 && el.scrollLeft > 0 || delta > 0 && el.scrollLeft < maxScrollLeft;
|
|
30
|
+
// 只在「容器還能橫向捲」時攔截,避免卡住頁面垂直捲動
|
|
31
|
+
if (willScroll) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
el.scrollLeft = Math.max(0, Math.min(maxScrollLeft, next));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
el.addEventListener('wheel', onWheel, {
|
|
37
|
+
passive: false
|
|
38
|
+
});
|
|
39
|
+
return ()=>el.removeEventListener('wheel', onWheel);
|
|
40
|
+
}, [
|
|
41
|
+
speed,
|
|
42
|
+
respectShiftKey
|
|
43
|
+
]);
|
|
44
|
+
return ref;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
exports.useHorizontalWheel = useHorizontalWheel;
|
package/dist/es/client.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { ReactNode } from 'react';
|
|
2
3
|
import { AtLeastOne } from './types.mjs';
|
|
3
4
|
|
|
4
5
|
type TStoreProps<S> = {
|
|
@@ -88,7 +89,7 @@ type TInitialProps$1<S> = TStoreProps<S> & {
|
|
|
88
89
|
* ```
|
|
89
90
|
*/
|
|
90
91
|
declare const createStoreContext: <S>() => {
|
|
91
|
-
StoreProvider: ({ children, store, handleChangeStore, defaultMerge, }:
|
|
92
|
+
StoreProvider: ({ children, store, handleChangeStore, defaultMerge, }: React__default.PropsWithChildren<Partial<TInitialProps$1<S>>>) => React__default.JSX.Element;
|
|
92
93
|
useStore: <T>(selector: (state: TStoreState<S>) => T, equalityFn?: (left: T, right: T) => boolean) => T;
|
|
93
94
|
};
|
|
94
95
|
|
|
@@ -121,7 +122,7 @@ type TInitialProps<Q> = Partial<TQueryProps<Q> & {
|
|
|
121
122
|
*
|
|
122
123
|
* @deprecated use `StoreProvider` from `core/store` instead
|
|
123
124
|
*/
|
|
124
|
-
declare const QueryProvider: <Q>({ children, query, handleChangeQuery, }:
|
|
125
|
+
declare const QueryProvider: <Q>({ children, query, handleChangeQuery, }: React__default.PropsWithChildren<TInitialProps<Q>>) => React__default.JSX.Element;
|
|
125
126
|
/**
|
|
126
127
|
* hook to get the store from the context
|
|
127
128
|
*
|
|
@@ -180,7 +181,7 @@ declare const createDataContext: <T>() => {
|
|
|
180
181
|
readonly DataProvider: ({ children, value }: {
|
|
181
182
|
children: ReactNode;
|
|
182
183
|
value: T;
|
|
183
|
-
}) =>
|
|
184
|
+
}) => React__default.JSX.Element;
|
|
184
185
|
};
|
|
185
186
|
|
|
186
187
|
type TCountdownActions = {
|
|
@@ -227,6 +228,27 @@ type UseDisclosureReturn = {
|
|
|
227
228
|
*/
|
|
228
229
|
declare function useDisclosure(initialState?: boolean): UseDisclosureReturn;
|
|
229
230
|
|
|
231
|
+
type Options = {
|
|
232
|
+
/** 滾輪轉成水平位移的倍率(觸控板通常較小,滑鼠滾輪較大) */
|
|
233
|
+
speed?: number;
|
|
234
|
+
/** 是否在按住 Shift 時改走瀏覽器原生(通常 Shift+滾輪就是橫向) */
|
|
235
|
+
respectShiftKey?: boolean;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* 將 element 的垂直滾輪事件轉成水平捲動的 hook
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
*
|
|
242
|
+
* ```tsx
|
|
243
|
+
* const ref = useHorizontalWheel();
|
|
244
|
+
*
|
|
245
|
+
* return <div ref={ref}>...</div>;
|
|
246
|
+
*
|
|
247
|
+
* // 你將可以用垂直滾輪來水平捲動這個 div 了!
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
declare const useHorizontalWheel: <T extends HTMLElement>({ speed, respectShiftKey, }?: Options) => React.RefObject<T | null>;
|
|
251
|
+
|
|
230
252
|
type TValueOptions<T> = AtLeastOne<{
|
|
231
253
|
/**
|
|
232
254
|
* The controlled value.
|
|
@@ -250,7 +272,7 @@ type TValueOptions<T> = AtLeastOne<{
|
|
|
250
272
|
*/
|
|
251
273
|
declare const useValue: <T>({ value, defaultValue }: TValueOptions<T>) => readonly [T, (newValue: T) => void];
|
|
252
274
|
|
|
253
|
-
declare function mergeRefs<T = any>(refs: Array<
|
|
275
|
+
declare function mergeRefs<T = any>(refs: Array<React__default.MutableRefObject<T> | React__default.LegacyRef<T> | undefined | null>): React__default.RefCallback<T>;
|
|
254
276
|
|
|
255
277
|
/**
|
|
256
278
|
* Downloads a file from a given source.
|
|
@@ -285,5 +307,5 @@ declare const getLocalStorage: <T>(key: string, deCode?: boolean) => T | undefin
|
|
|
285
307
|
*/
|
|
286
308
|
declare const setLocalStorage: (key: string, value: Record<string, any>, enCode?: boolean) => void;
|
|
287
309
|
|
|
288
|
-
export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useQueryContext, useValue };
|
|
310
|
+
export { QueryProvider, createDataContext, createStoreContext, downloadFile, getLocalStorage, mergeRefs, setLocalStorage, useCountdown, useDisclosure, useHorizontalWheel, useQueryContext, useValue };
|
|
289
311
|
export type { TCountdownActions, UseDisclosureReturn };
|
package/dist/es/client.mjs
CHANGED
|
@@ -3,6 +3,7 @@ export { Q as QueryProvider, u as useQueryContext } from './queryStore-12s-Dkb-A
|
|
|
3
3
|
import React, { useMemo, createContext, useContext, useState, useCallback } from 'react';
|
|
4
4
|
export { u as useCountdown } from './useCountdown-12s-t52WIHfq.mjs';
|
|
5
5
|
export { u as useDisclosure } from './useDisclosure-12s-BQAHpAXK.mjs';
|
|
6
|
+
export { u as useHorizontalWheel } from './useHorizontalWheel-12s-D3IfutV9.mjs';
|
|
6
7
|
export { d as downloadFile } from './download-12s-CnaJ0p_f.mjs';
|
|
7
8
|
export { g as getLocalStorage, s as setLocalStorage } from './webStorage-12s-Bo7x8q5t.mjs';
|
|
8
9
|
|
package/dist/es/index.d.mts
CHANGED
|
@@ -706,6 +706,35 @@ declare function invariant(condition: any, message?: string | (() => string)): a
|
|
|
706
706
|
*/
|
|
707
707
|
declare const isServer: () => boolean;
|
|
708
708
|
|
|
709
|
+
type MaskKeySelector = string | ((key: string, value: unknown, path: Array<string | number>) => boolean);
|
|
710
|
+
/**
|
|
711
|
+
* 深度走訪物件/陣列,將指定鍵名且值為字串的欄位遮罩為固定字串(預設 "******")。
|
|
712
|
+
*
|
|
713
|
+
* - 預設僅遮罩鍵名等於 `password` 的欄位(區分大小寫)
|
|
714
|
+
* - 可傳入「字串鍵名」或「函式選擇器」來自訂要遮罩的欄位
|
|
715
|
+
* - 僅在值為字串時遮罩,其他型別維持原樣
|
|
716
|
+
* - 不變更輸入參考;回傳全新的資料結構
|
|
717
|
+
* - 函式選擇器會收到 `key`、`value` 與 `path`(到該鍵的路徑,陣列用索引)
|
|
718
|
+
* - 支援巢狀物件與陣列
|
|
719
|
+
*
|
|
720
|
+
* @param input - 來源資料
|
|
721
|
+
* @param mask - 遮罩字串,預設為 "******"
|
|
722
|
+
* @param selector - 欄位選擇器,字串或函式,預設 'password'
|
|
723
|
+
* @returns 回傳遮罩後的新資料
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* const input = { password: 'secret', user: { password: 'abc' }, list: [{ password: 'x' }, { ok: 1 }] };
|
|
727
|
+
* const output = maskPasswords(input);
|
|
728
|
+
* // { password: '******', user: { password: '******' }, list: [{ password: '******' }, { ok: 1 }] }
|
|
729
|
+
*
|
|
730
|
+
* // 指定鍵名
|
|
731
|
+
* maskPasswords({ secret: 'xxx' }, '***', 'secret'); // { secret: '***' }
|
|
732
|
+
*
|
|
733
|
+
* // 使用函式選擇器(鍵名包含 'pass' 都遮)
|
|
734
|
+
* maskPasswords({ password: 'a', passcode: 'b' }, '***', (k) => k.includes('pass')); // { password: '***', passcode: '***' }
|
|
735
|
+
*/
|
|
736
|
+
declare const maskPasswords: <T>(input: T, mask?: string, selector?: MaskKeySelector) => T;
|
|
737
|
+
|
|
709
738
|
type DeepPartialWithBooleanOverride<T> = {
|
|
710
739
|
[K in keyof T]?: T[K] extends object ? T[K] extends boolean ? T[K] : DeepPartialWithBooleanOverride<T[K]> | boolean : T[K];
|
|
711
740
|
};
|
|
@@ -1232,5 +1261,5 @@ declare const generatePeriodArray: () => string[];
|
|
|
1232
1261
|
*/
|
|
1233
1262
|
declare const getCurrentPeriod: () => string;
|
|
1234
1263
|
|
|
1235
|
-
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
|
1236
|
-
export type { MimeTypeExtension, MimeTypeValue, PartialBy, RequiredBy };
|
|
1264
|
+
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, maskPasswords, maskString, mergeConfig, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
|
1265
|
+
export type { MaskKeySelector, MimeTypeExtension, MimeTypeValue, PartialBy, RequiredBy };
|
package/dist/es/index.mjs
CHANGED
|
@@ -487,6 +487,74 @@ message) {
|
|
|
487
487
|
* 判斷執行環境是否為 Server(node.js) 端
|
|
488
488
|
*/ const isServer = ()=>typeof window === 'undefined';
|
|
489
489
|
|
|
490
|
+
/**
|
|
491
|
+
* 深度走訪物件/陣列,將指定鍵名且值為字串的欄位遮罩為固定字串(預設 "******")。
|
|
492
|
+
*
|
|
493
|
+
* - 預設僅遮罩鍵名等於 `password` 的欄位(區分大小寫)
|
|
494
|
+
* - 可傳入「字串鍵名」或「函式選擇器」來自訂要遮罩的欄位
|
|
495
|
+
* - 僅在值為字串時遮罩,其他型別維持原樣
|
|
496
|
+
* - 不變更輸入參考;回傳全新的資料結構
|
|
497
|
+
* - 函式選擇器會收到 `key`、`value` 與 `path`(到該鍵的路徑,陣列用索引)
|
|
498
|
+
* - 支援巢狀物件與陣列
|
|
499
|
+
*
|
|
500
|
+
* @param input - 來源資料
|
|
501
|
+
* @param mask - 遮罩字串,預設為 "******"
|
|
502
|
+
* @param selector - 欄位選擇器,字串或函式,預設 'password'
|
|
503
|
+
* @returns 回傳遮罩後的新資料
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* const input = { password: 'secret', user: { password: 'abc' }, list: [{ password: 'x' }, { ok: 1 }] };
|
|
507
|
+
* const output = maskPasswords(input);
|
|
508
|
+
* // { password: '******', user: { password: '******' }, list: [{ password: '******' }, { ok: 1 }] }
|
|
509
|
+
*
|
|
510
|
+
* // 指定鍵名
|
|
511
|
+
* maskPasswords({ secret: 'xxx' }, '***', 'secret'); // { secret: '***' }
|
|
512
|
+
*
|
|
513
|
+
* // 使用函式選擇器(鍵名包含 'pass' 都遮)
|
|
514
|
+
* maskPasswords({ password: 'a', passcode: 'b' }, '***', (k) => k.includes('pass')); // { password: '***', passcode: '***' }
|
|
515
|
+
*/ const maskPasswords = (input, mask = '******', selector = 'password')=>{
|
|
516
|
+
const isObject = (val)=>val !== null && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date);
|
|
517
|
+
const shouldMask = (key, value, path)=>{
|
|
518
|
+
if (typeof selector === 'string') {
|
|
519
|
+
return key === selector && typeof value === 'string';
|
|
520
|
+
}
|
|
521
|
+
return selector(key, value, path) && typeof value === 'string';
|
|
522
|
+
};
|
|
523
|
+
const walk = (val, path)=>{
|
|
524
|
+
if (val === null || val === undefined) return val;
|
|
525
|
+
if (Array.isArray(val)) {
|
|
526
|
+
return val.map((item, idx)=>walk(item, [
|
|
527
|
+
...path,
|
|
528
|
+
idx
|
|
529
|
+
]));
|
|
530
|
+
}
|
|
531
|
+
if (val instanceof Date) {
|
|
532
|
+
return new Date(val.getTime());
|
|
533
|
+
}
|
|
534
|
+
if (isObject(val)) {
|
|
535
|
+
const result = Object.entries(val).reduce((acc, [key, v])=>{
|
|
536
|
+
const nextPath = [
|
|
537
|
+
...path,
|
|
538
|
+
key
|
|
539
|
+
];
|
|
540
|
+
if (shouldMask(key, v, nextPath)) {
|
|
541
|
+
return {
|
|
542
|
+
...acc,
|
|
543
|
+
[key]: mask
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
...acc,
|
|
548
|
+
[key]: walk(v, nextPath)
|
|
549
|
+
};
|
|
550
|
+
}, {});
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
return val;
|
|
554
|
+
};
|
|
555
|
+
return walk(input, []);
|
|
556
|
+
};
|
|
557
|
+
|
|
490
558
|
/**
|
|
491
559
|
* 將物件中的某些 key 排除
|
|
492
560
|
*
|
|
@@ -1397,4 +1465,4 @@ const FILE_SIZE_UNITS = [
|
|
|
1397
1465
|
return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
|
|
1398
1466
|
};
|
|
1399
1467
|
|
|
1400
|
-
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
|
1468
|
+
export { ByteSize, MimeTypeMap, OtherMimeType, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, decodeBase64, decodeJson, deepClone, deepMerge, encodeBase64, encodeJson, 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, maskPasswords, maskString, mergeConfig, objectToSearchParams, omit, omitByValue, parseFileInfoFromFilename, parseFilenameFromDisposition, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, renameKey, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType, wait };
|
package/dist/es/types.d.mts
CHANGED
|
@@ -137,6 +137,8 @@ type OnlyOne<T> = {
|
|
|
137
137
|
[P in Exclude<keyof T, K>]?: never;
|
|
138
138
|
};
|
|
139
139
|
}[keyof T];
|
|
140
|
+
type Builtin = string | number | boolean | bigint | symbol | null | undefined;
|
|
141
|
+
type NonRecursive = Builtin | Function | Date | RegExp | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any> | Promise<any>;
|
|
140
142
|
/**
|
|
141
143
|
* A utility type that makes all properties in the generic type `T` optional. Also, it makes all nested properties optional.
|
|
142
144
|
*
|
|
@@ -154,9 +156,9 @@ type OnlyOne<T> = {
|
|
|
154
156
|
*
|
|
155
157
|
* const obj2: ExampleDeepPartial = { a: { b: 'world' } }; // OK
|
|
156
158
|
*/
|
|
157
|
-
type DeepPartial<T> =
|
|
158
|
-
[
|
|
159
|
-
}
|
|
159
|
+
type DeepPartial<T> = T extends NonRecursive ? T : T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? {
|
|
160
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
161
|
+
} : T;
|
|
160
162
|
/**
|
|
161
163
|
* 將 Obj 指定的 key 轉換成指定的型別
|
|
162
164
|
*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 將 element 的垂直滾輪事件轉成水平捲動的 hook
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const ref = useHorizontalWheel();
|
|
11
|
+
*
|
|
12
|
+
* return <div ref={ref}>...</div>;
|
|
13
|
+
*
|
|
14
|
+
* // 你將可以用垂直滾輪來水平捲動這個 div 了!
|
|
15
|
+
* ```
|
|
16
|
+
*/ const useHorizontalWheel = ({ speed = 1, respectShiftKey = true } = {})=>{
|
|
17
|
+
const ref = useRef(null);
|
|
18
|
+
useEffect(()=>{
|
|
19
|
+
const el = ref.current;
|
|
20
|
+
if (!el) return undefined;
|
|
21
|
+
const onWheel = (e)=>{
|
|
22
|
+
if (respectShiftKey && e.shiftKey) return;
|
|
23
|
+
// deltaY 是「想往下滾」的意圖,我們把它轉成 scrollLeft
|
|
24
|
+
const delta = e.deltaY * speed;
|
|
25
|
+
const maxScrollLeft = el.scrollWidth - el.clientWidth;
|
|
26
|
+
const next = el.scrollLeft + delta;
|
|
27
|
+
const canScrollHorizontally = el.scrollWidth > el.clientWidth;
|
|
28
|
+
if (!canScrollHorizontally) return;
|
|
29
|
+
const willScroll = delta < 0 && el.scrollLeft > 0 || delta > 0 && el.scrollLeft < maxScrollLeft;
|
|
30
|
+
// 只在「容器還能橫向捲」時攔截,避免卡住頁面垂直捲動
|
|
31
|
+
if (willScroll) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
el.scrollLeft = Math.max(0, Math.min(maxScrollLeft, next));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
el.addEventListener('wheel', onWheel, {
|
|
37
|
+
passive: false
|
|
38
|
+
});
|
|
39
|
+
return ()=>el.removeEventListener('wheel', onWheel);
|
|
40
|
+
}, [
|
|
41
|
+
speed,
|
|
42
|
+
respectShiftKey
|
|
43
|
+
]);
|
|
44
|
+
return ref;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { useHorizontalWheel as u };
|