@pawover/kit 0.0.0-beta.2 → 0.0.0-beta.20
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/enums-Bv0coVvD.js +97 -0
- package/dist/enums-Bv0coVvD.js.map +1 -0
- package/dist/enums.d.ts +2 -25
- package/dist/enums.js +2 -24
- package/dist/except-C38JazcR.d.ts +971 -0
- package/dist/except-C38JazcR.d.ts.map +1 -0
- package/dist/hooks-alova.js.map +1 -1
- package/dist/hooks-react.d.ts +7 -30
- package/dist/hooks-react.d.ts.map +1 -1
- package/dist/hooks-react.js +7 -189
- package/dist/hooks-react.js.map +1 -1
- package/dist/index-BKUSlLXA.d.ts +159 -0
- package/dist/index-BKUSlLXA.d.ts.map +1 -0
- package/dist/index-CjoMz104.d.ts +21 -0
- package/dist/index-CjoMz104.d.ts.map +1 -0
- package/dist/index.d.ts +2070 -1066
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1429
- package/dist/patches-fetchEventSource.d.ts +2 -721
- package/dist/patches-fetchEventSource.d.ts.map +1 -1
- package/dist/patches-fetchEventSource.js +1 -114
- package/dist/patches-fetchEventSource.js.map +1 -1
- package/dist/utils-khUJYSj2.js +1989 -0
- package/dist/utils-khUJYSj2.js.map +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/dist/zod.d.ts +5 -1
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +6 -1
- package/dist/zod.js.map +1 -1
- package/metadata.json +15 -3
- package/package.json +20 -17
- package/dist/enums.d.ts.map +0 -1
- package/dist/enums.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,1989 @@
|
|
|
1
|
+
//#region src/utils/typeof/types.ts
|
|
2
|
+
const PROTOTYPE_TAGS = {
|
|
3
|
+
abortSignal: "[object AbortSignal]",
|
|
4
|
+
array: "[object Array]",
|
|
5
|
+
asyncFunction: "[object AsyncFunction]",
|
|
6
|
+
asyncGeneratorFunction: "[object AsyncGeneratorFunction]",
|
|
7
|
+
bigInt: "[object BigInt]",
|
|
8
|
+
blob: "[object Blob]",
|
|
9
|
+
boolean: "[object Boolean]",
|
|
10
|
+
date: "[object Date]",
|
|
11
|
+
error: "[object Error]",
|
|
12
|
+
file: "[object File]",
|
|
13
|
+
function: "[object Function]",
|
|
14
|
+
generatorFunction: "[object GeneratorFunction]",
|
|
15
|
+
iframe: "[object HTMLIFrameElement]",
|
|
16
|
+
map: "[object Map]",
|
|
17
|
+
null: "[object Null]",
|
|
18
|
+
number: "[object Number]",
|
|
19
|
+
object: "[object Object]",
|
|
20
|
+
promise: "[object Promise]",
|
|
21
|
+
readableStream: "[object ReadableStream]",
|
|
22
|
+
regExp: "[object RegExp]",
|
|
23
|
+
set: "[object Set]",
|
|
24
|
+
string: "[object String]",
|
|
25
|
+
symbol: "[object Symbol]",
|
|
26
|
+
undefined: "[object Undefined]",
|
|
27
|
+
URLSearchParams: "[object URLSearchParams]",
|
|
28
|
+
weakMap: "[object WeakMap]",
|
|
29
|
+
weakSet: "[object WeakSet]",
|
|
30
|
+
webSocket: "[object WebSocket]",
|
|
31
|
+
window: "[object Window]"
|
|
32
|
+
};
|
|
33
|
+
const TYPED_ARRAY_TAGS = new Set([
|
|
34
|
+
"[object Int8Array]",
|
|
35
|
+
"[object Uint8Array]",
|
|
36
|
+
"[object Uint8ClampedArray]",
|
|
37
|
+
"[object Int16Array]",
|
|
38
|
+
"[object Uint16Array]",
|
|
39
|
+
"[object Int32Array]",
|
|
40
|
+
"[object Uint32Array]",
|
|
41
|
+
"[object Float32Array]",
|
|
42
|
+
"[object Float64Array]",
|
|
43
|
+
"[object BigInt64Array]",
|
|
44
|
+
"[object BigUint64Array]"
|
|
45
|
+
]);
|
|
46
|
+
function resolvePrototypeString(value) {
|
|
47
|
+
return Object.prototype.toString.call(value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/utils/typeof/isAbortSignal.ts
|
|
52
|
+
/**
|
|
53
|
+
* 检查 value 是否为 AbortSignal
|
|
54
|
+
* @param value 待检查值
|
|
55
|
+
* @returns 是否为 AbortSignal
|
|
56
|
+
*/
|
|
57
|
+
function isAbortSignal(value) {
|
|
58
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.abortSignal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/utils/typeof/isArray.ts
|
|
63
|
+
/**
|
|
64
|
+
* 检查 value 是否为数组
|
|
65
|
+
*
|
|
66
|
+
* @param value 待检查值
|
|
67
|
+
* @returns 是否为数组
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* isArray([]); // true
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function isArray(value) {
|
|
74
|
+
return Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 检查 value 是否为 TypedArray
|
|
78
|
+
*
|
|
79
|
+
* @param value 待检查值
|
|
80
|
+
* @returns 是否为 TypedArray
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* isTypedArray(new Int8Array()); // true
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
function isTypedArray(value) {
|
|
87
|
+
return typeof value === "object" && value !== null && TYPED_ARRAY_TAGS.has(resolvePrototypeString(value));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/utils/typeof/isBigInt.ts
|
|
92
|
+
/**
|
|
93
|
+
* 检查 value 是否为 BigInt
|
|
94
|
+
* @param value 待检查值
|
|
95
|
+
* @returns 是否为 BigInt
|
|
96
|
+
*/
|
|
97
|
+
function isBigInt(value) {
|
|
98
|
+
return typeof value === "bigint";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/utils/typeof/isBlob.ts
|
|
103
|
+
/**
|
|
104
|
+
* 检查 value 是否为 Blob
|
|
105
|
+
* @param value 待检查值
|
|
106
|
+
* @returns 是否为 Blob
|
|
107
|
+
*/
|
|
108
|
+
function isBlob(value) {
|
|
109
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.blob;
|
|
110
|
+
}
|
|
111
|
+
function isFile(value) {
|
|
112
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.file;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/utils/typeof/isBoolean.ts
|
|
117
|
+
/**
|
|
118
|
+
* 检查 value 是否为 Boolean
|
|
119
|
+
* @param value 待检查值
|
|
120
|
+
* @returns 是否为 Boolean
|
|
121
|
+
*/
|
|
122
|
+
function isBoolean(value) {
|
|
123
|
+
return typeof value === "boolean";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/utils/typeof/isFunction.ts
|
|
128
|
+
/**
|
|
129
|
+
* 检查 value 是否为 Function
|
|
130
|
+
* @param value 待检查值
|
|
131
|
+
* @returns 是否为 Function
|
|
132
|
+
*/
|
|
133
|
+
function isFunction(value) {
|
|
134
|
+
return typeof value === "function";
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 检查 value 是否为 AsyncFunction
|
|
138
|
+
* @param value 待检查值
|
|
139
|
+
* @returns 是否为 AsyncFunction
|
|
140
|
+
*/
|
|
141
|
+
function isAsyncFunction(value) {
|
|
142
|
+
return isFunction(value) && resolvePrototypeString(value) === PROTOTYPE_TAGS.asyncFunction;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 检查 value 是否为 GeneratorFunction
|
|
146
|
+
* @param value 待检查值
|
|
147
|
+
* @returns 是否为 GeneratorFunction
|
|
148
|
+
*/
|
|
149
|
+
function isGeneratorFunction(value) {
|
|
150
|
+
return isFunction(value) && resolvePrototypeString(value) === PROTOTYPE_TAGS.generatorFunction;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 检查 value 是否为 AsyncGeneratorFunction
|
|
154
|
+
* @param value 待检查值
|
|
155
|
+
* @returns 是否为 AsyncGeneratorFunction
|
|
156
|
+
*/
|
|
157
|
+
function isAsyncGeneratorFunction(value) {
|
|
158
|
+
return isFunction(value) && resolvePrototypeString(value) === PROTOTYPE_TAGS.asyncGeneratorFunction;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/utils/typeof/isClass.ts
|
|
163
|
+
function isConstructable(fn) {
|
|
164
|
+
try {
|
|
165
|
+
Reflect.construct(fn, []);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 检查 value 是否为 Class
|
|
173
|
+
*
|
|
174
|
+
* @param value 待检查值
|
|
175
|
+
* @returns 是否为 Class
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* class A {}
|
|
179
|
+
* isClass(A); // true
|
|
180
|
+
* isClass(() => {}); // false
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
function isClass(value) {
|
|
184
|
+
return isFunction(value) && !isAsyncFunction(value) && Function.prototype.toString.call(value).startsWith("class ") && isConstructable(value) && value.prototype !== void 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/utils/typeof/isDate.ts
|
|
189
|
+
/**
|
|
190
|
+
* 检查 value 是否为 Date 对象
|
|
191
|
+
*
|
|
192
|
+
* @param value 待检查值
|
|
193
|
+
* @param invalidCheck 是否要求日期有效(非 Invalid Date)。默认 true
|
|
194
|
+
* - true: 仅当是有效 Date 对象时返回 true(排除 new Date('invalid'))
|
|
195
|
+
* - false: 只要 [[Prototype]] 是 Date 即返回 true(包含 Invalid Date)
|
|
196
|
+
* @returns 是否为 Date 对象,根据 invalidCheck 返回不同语义的 Date 判定
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* isDate(new Date()); // true
|
|
201
|
+
* isDate(new Date('invalid')); // false
|
|
202
|
+
* isDate(new Date('invalid'), false); // true
|
|
203
|
+
* isDate(null); // false
|
|
204
|
+
* isDate({}); // false
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
function isDate(value, invalidCheck = true) {
|
|
208
|
+
if (!value || typeof value !== "object") return false;
|
|
209
|
+
if (resolvePrototypeString(value) !== PROTOTYPE_TAGS.date) return false;
|
|
210
|
+
if (!invalidCheck) return true;
|
|
211
|
+
try {
|
|
212
|
+
const time = value.getTime();
|
|
213
|
+
return typeof time === "number" && !Number.isNaN(time);
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/utils/typeof/isEnumeration.ts
|
|
221
|
+
/**
|
|
222
|
+
* 判断一个值是否为有效的枚举
|
|
223
|
+
* - 枚举不能为空
|
|
224
|
+
* - 枚举所有的值必须是 string 或 number
|
|
225
|
+
*
|
|
226
|
+
* @param enumeration 待检查值
|
|
227
|
+
* @returns 是否为有效的枚举
|
|
228
|
+
*/
|
|
229
|
+
function isEnumeration(enumeration) {
|
|
230
|
+
if (!isObject(enumeration)) return [false, false];
|
|
231
|
+
const keys = Object.keys(enumeration);
|
|
232
|
+
if (!keys.length) return [false, false];
|
|
233
|
+
const values = Object.values(enumeration);
|
|
234
|
+
if (!values.every((v) => typeof v === "string" || typeof v === "number")) return [false, false];
|
|
235
|
+
return [true, keys.every((k) => values.some((v) => v.toString() === k))];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/utils/typeof/isEqual.ts
|
|
240
|
+
/**
|
|
241
|
+
* 深度比较两个值是否相等
|
|
242
|
+
*
|
|
243
|
+
* @param value 待比较值 A
|
|
244
|
+
* @param other 待比较值 B
|
|
245
|
+
* @returns 是否相等
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* isEqual({ a: 1 }, { a: 1 }); // true
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
function isEqual(x, y) {
|
|
252
|
+
if (Object.is(x, y)) return true;
|
|
253
|
+
if (isDate(x) && isDate(y)) return x.getTime() === y.getTime();
|
|
254
|
+
if (isRegExp(x) && isRegExp(y)) return x.toString() === y.toString();
|
|
255
|
+
if (typeof x !== "object" || x === null || typeof y !== "object" || y === null) return false;
|
|
256
|
+
const keysX = Reflect.ownKeys(x);
|
|
257
|
+
const keysY = Reflect.ownKeys(y);
|
|
258
|
+
if (keysX.length !== keysY.length) return false;
|
|
259
|
+
for (const key of keysX) {
|
|
260
|
+
if (!Reflect.has(y, key)) return false;
|
|
261
|
+
if (!isEqual(x[key], y[key])) return false;
|
|
262
|
+
}
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/utils/typeof/isError.ts
|
|
268
|
+
/**
|
|
269
|
+
* 检查 value 是否为 Error 对象
|
|
270
|
+
* @param value 待检查值
|
|
271
|
+
* @returns 是否为 Error
|
|
272
|
+
*/
|
|
273
|
+
function isError(value) {
|
|
274
|
+
return value instanceof Error || resolvePrototypeString(value) === PROTOTYPE_TAGS.error;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/utils/typeof/isFalsy.ts
|
|
279
|
+
/**
|
|
280
|
+
* 检查 value 是否为 Falsy 值 (false, 0, "", null, undefined, NaN)
|
|
281
|
+
* @param value 待检查值
|
|
282
|
+
* @returns 是否为 Falsy
|
|
283
|
+
*/
|
|
284
|
+
function isFalsy(value) {
|
|
285
|
+
if (isNaN(value) || isNull(value) || isUndefined(value)) return true;
|
|
286
|
+
return value === false || value === 0 || value === 0n || value === "";
|
|
287
|
+
}
|
|
288
|
+
function isFalsyLike(value) {
|
|
289
|
+
if (isFalsy(value)) return true;
|
|
290
|
+
return typeof value === "string" && (value === "null" || value === "undefined" || value === "NaN" || value === "false" || value === "0" || value === "0n");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/utils/typeof/isIterable.ts
|
|
295
|
+
/**
|
|
296
|
+
* 检查 value 是否为可迭代对象 (Iterable)
|
|
297
|
+
* @param value 待检查值
|
|
298
|
+
* @returns 是否为 Iterable
|
|
299
|
+
*/
|
|
300
|
+
function isIterable(value) {
|
|
301
|
+
return !!value && typeof value[Symbol.iterator] === "function";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/utils/typeof/isMap.ts
|
|
306
|
+
/**
|
|
307
|
+
* 检查 value 是否为 Map
|
|
308
|
+
* @param value 待检查值
|
|
309
|
+
* @returns 是否为 Map
|
|
310
|
+
*/
|
|
311
|
+
function isMap(value) {
|
|
312
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.map;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 检查 value 是否为 WeakMap
|
|
316
|
+
* @param value 待检查值
|
|
317
|
+
* @returns 是否为 WeakMap
|
|
318
|
+
*/
|
|
319
|
+
function isWeakMap(value) {
|
|
320
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.weakMap;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/utils/typeof/isNull.ts
|
|
325
|
+
/**
|
|
326
|
+
* 检查 value 是否为 null
|
|
327
|
+
* @param value 待检查值
|
|
328
|
+
* @returns 是否为 null
|
|
329
|
+
*/
|
|
330
|
+
function isNull(value) {
|
|
331
|
+
return value === null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/utils/typeof/isNumber.ts
|
|
336
|
+
/**
|
|
337
|
+
* 检查 value 是否为 number 类型
|
|
338
|
+
*
|
|
339
|
+
* @param value 待检查值
|
|
340
|
+
* @param NaNCheck 是否排除 `NaN`,默认为 `true`
|
|
341
|
+
* @returns 是否为 number
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* isNumber(1); // true
|
|
345
|
+
* isNumber(NaN); // false (default)
|
|
346
|
+
* isNumber(NaN, false); // true
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
function isNumber(value, NaNCheck = true) {
|
|
350
|
+
return typeof value === "number" && (!NaNCheck || !isNaN(value));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 检查 value 是否为 NaN
|
|
354
|
+
*
|
|
355
|
+
* @param value 待检查值
|
|
356
|
+
* @returns 是否为 NaN
|
|
357
|
+
*/
|
|
358
|
+
function isNaN(value) {
|
|
359
|
+
return Number.isNaN(value);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 检查 value 是否为整数
|
|
363
|
+
*
|
|
364
|
+
* @param value 待检查值
|
|
365
|
+
* @param safeCheck 是否附加安全数检查
|
|
366
|
+
* @returns 是否为整数
|
|
367
|
+
*/
|
|
368
|
+
function isInteger(value, safeCheck = true) {
|
|
369
|
+
const check = Number.isInteger(value);
|
|
370
|
+
return safeCheck ? check && Number.isSafeInteger(value) : check;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 检查 value 是否为正整数
|
|
374
|
+
* - 此函数中 `0` 不被视为正整数
|
|
375
|
+
*
|
|
376
|
+
* @param value 待检查值
|
|
377
|
+
* @param safeCheck 是否附加安全数检查
|
|
378
|
+
*/
|
|
379
|
+
function isPositiveInteger(value, safeCheck = true) {
|
|
380
|
+
return isInteger(value, safeCheck) && value > 0;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* 检查 value 是否为负整数
|
|
384
|
+
* - 此函数中 `0` 不被视为负整数
|
|
385
|
+
*
|
|
386
|
+
* @param value 待检查值
|
|
387
|
+
* @param safeCheck 是否附加安全数检查
|
|
388
|
+
*/
|
|
389
|
+
function isNegativeInteger(value, safeCheck = true) {
|
|
390
|
+
return isInteger(value, safeCheck) && value < 0;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 检查 value 是否为 Infinity
|
|
394
|
+
* - 排除 `NaN`
|
|
395
|
+
*
|
|
396
|
+
* @param value 待检查值
|
|
397
|
+
*/
|
|
398
|
+
function isInfinity(value) {
|
|
399
|
+
return isNumber(value) && (Number.POSITIVE_INFINITY === value || Number.NEGATIVE_INFINITY === value);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* 检查 value 是否类似 Infinity
|
|
403
|
+
* - 排除 `NaN`
|
|
404
|
+
*
|
|
405
|
+
* @param value 待检查值
|
|
406
|
+
*/
|
|
407
|
+
function isInfinityLike(value) {
|
|
408
|
+
const check = isInfinity(value);
|
|
409
|
+
if (check) return check;
|
|
410
|
+
if (typeof value === "string") {
|
|
411
|
+
const v = value.trim().toLowerCase();
|
|
412
|
+
return v === "infinity" || v === "-infinity" || v === "+infinity";
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/utils/typeof/isObject.ts
|
|
419
|
+
/**
|
|
420
|
+
* 判断是否为对象类型
|
|
421
|
+
* - 可选是否检查原型为 `Object.prototype`,防止原型链污染
|
|
422
|
+
*
|
|
423
|
+
* @param value 待检查值
|
|
424
|
+
* @param prototypeCheck 是否进行原型检查,默认 `true`
|
|
425
|
+
* @returns 是否为 Plain Object (当 checkPrototype=true) 或 object
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* isObject({}); // true
|
|
429
|
+
* isObject(new Date()); // false (because prototype is not Object.prototype)
|
|
430
|
+
* isObject(new Date(), false); // true (is object type)
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
function isObject(value, prototypeCheck = true) {
|
|
434
|
+
const check = resolvePrototypeString(value) === PROTOTYPE_TAGS.object;
|
|
435
|
+
return prototypeCheck ? check && Object.getPrototypeOf(value) === Object.prototype : check;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/utils/typeof/isPromise.ts
|
|
440
|
+
/**
|
|
441
|
+
* 检查 value 是否为 Promise
|
|
442
|
+
* @param value 待检查值
|
|
443
|
+
* @returns 是否为 Promise
|
|
444
|
+
*/
|
|
445
|
+
function isPromise(value) {
|
|
446
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.promise;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 检查 value 是否为 PromiseLike (thenable)
|
|
450
|
+
* @param value 待检查值
|
|
451
|
+
* @returns 是否为 PromiseLike
|
|
452
|
+
*/
|
|
453
|
+
function isPromiseLike(value) {
|
|
454
|
+
return isPromise(value) || isObject(value) && isFunction(value["then"]);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/utils/typeof/isReadableStream.ts
|
|
459
|
+
/**
|
|
460
|
+
* Checks if a value is a WHATWG ReadableStream instance.
|
|
461
|
+
*
|
|
462
|
+
* - Uses `Object.prototype.toString` where supported (modern browsers, Node.js ≥18).
|
|
463
|
+
* - Falls back to duck-typing in older environments.
|
|
464
|
+
* - Resistant to basic forgery, but not 100% secure in all polyfill scenarios.
|
|
465
|
+
*
|
|
466
|
+
* ⚠️ Note: In older Node.js (<18) or with non-compliant polyfills, this may return false positives or negatives.
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* 检查 value 是否为 ReadableStream
|
|
470
|
+
* @param value 待检查值
|
|
471
|
+
* @returns 是否为 ReadableStream
|
|
472
|
+
*/
|
|
473
|
+
function isReadableStream(value) {
|
|
474
|
+
if (resolvePrototypeString(value) === PROTOTYPE_TAGS.readableStream) return true;
|
|
475
|
+
return isObject(value) && isFunction(value["getReader"]) && isFunction(value["pipeThrough"]);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/utils/typeof/isString.ts
|
|
480
|
+
/**
|
|
481
|
+
* 检查 value 是否为 string 类型
|
|
482
|
+
*
|
|
483
|
+
* @param value 待检查值
|
|
484
|
+
* @param checkEmpty 是否排除空字符串
|
|
485
|
+
* @returns 是否为字符串
|
|
486
|
+
* @example
|
|
487
|
+
* ```ts
|
|
488
|
+
* isString("abc"); // true
|
|
489
|
+
* isString(""); // true
|
|
490
|
+
* isString("", true); // false
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
function isString(value, checkEmpty = false) {
|
|
494
|
+
return typeof value === "string" && (!checkEmpty || !!value.length);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/utils/typeof/isRegExp.ts
|
|
499
|
+
/**
|
|
500
|
+
* 检查 value 是否为 RegExp
|
|
501
|
+
* @param value 待检查值
|
|
502
|
+
* @returns 是否为 RegExp
|
|
503
|
+
*/
|
|
504
|
+
function isRegExp(value) {
|
|
505
|
+
if (typeof value !== "object" || value === null) return false;
|
|
506
|
+
try {
|
|
507
|
+
const regex = value;
|
|
508
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.regExp && isString(regex.source) && isString(regex.flags) && isBoolean(regex.global) && isFunction(regex.test);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/utils/typeof/isSet.ts
|
|
516
|
+
/**
|
|
517
|
+
* 检查 value 是否为 Set
|
|
518
|
+
* @param value 待检查值
|
|
519
|
+
* @returns 是否为 Set
|
|
520
|
+
*/
|
|
521
|
+
function isSet(value) {
|
|
522
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.set;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 检查 value 是否为 WeakSet
|
|
526
|
+
* @param value 待检查值
|
|
527
|
+
* @returns 是否为 WeakSet
|
|
528
|
+
*/
|
|
529
|
+
function isWeakSet(value) {
|
|
530
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.weakSet;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region src/utils/typeof/isSymbol.ts
|
|
535
|
+
/**
|
|
536
|
+
* 检查 value 是否为 Symbol
|
|
537
|
+
* @param value 待检查值
|
|
538
|
+
* @returns 是否为 Symbol
|
|
539
|
+
*/
|
|
540
|
+
function isSymbol(value) {
|
|
541
|
+
return typeof value === "symbol";
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/utils/typeof/isUndefined.ts
|
|
546
|
+
/**
|
|
547
|
+
* 检查 value 是否为 undefined
|
|
548
|
+
* @param value 待检查值
|
|
549
|
+
* @returns 是否为 undefined
|
|
550
|
+
*/
|
|
551
|
+
function isUndefined(value) {
|
|
552
|
+
return typeof value === "undefined";
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/utils/typeof/isURLSearchParams.ts
|
|
557
|
+
/**
|
|
558
|
+
* 检查 value 是否为 URLSearchParams
|
|
559
|
+
* @param value 待检查值
|
|
560
|
+
* @returns 是否为 URLSearchParams
|
|
561
|
+
*/
|
|
562
|
+
function isURLSearchParams(value) {
|
|
563
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.URLSearchParams;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/utils/typeof/isWebSocket.ts
|
|
568
|
+
/**
|
|
569
|
+
* 检查 value 是否为 WebSocket
|
|
570
|
+
* @param value 待检查值
|
|
571
|
+
* @returns 是否为 WebSocket
|
|
572
|
+
*/
|
|
573
|
+
function isWebSocket(value) {
|
|
574
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.webSocket;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/utils/typeof/isWindow.ts
|
|
579
|
+
/**
|
|
580
|
+
* 检查 value 是否为 Window
|
|
581
|
+
* @param value 待检查值
|
|
582
|
+
* @returns 是否为 Window
|
|
583
|
+
*/
|
|
584
|
+
function isWindow(value) {
|
|
585
|
+
return resolvePrototypeString(value) === PROTOTYPE_TAGS.window;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/utils/array/arrayCast.ts
|
|
590
|
+
function arrayCast(candidate, checkEmpty = true) {
|
|
591
|
+
if (checkEmpty && (isUndefined(candidate) || isNull(candidate))) return [];
|
|
592
|
+
return isArray(candidate) ? [...candidate] : [candidate];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/utils/array/arrayCompete.ts
|
|
597
|
+
/**
|
|
598
|
+
* 数组竞争
|
|
599
|
+
* - 返回在匹配函数的比较条件中获胜的最终项目,适用于更复杂的最小值/最大值计算
|
|
600
|
+
*
|
|
601
|
+
* @param initialList 数组
|
|
602
|
+
* @param match 匹配函数
|
|
603
|
+
* @returns 获胜的元素,如果数组为空或参数无效则返回 `null`
|
|
604
|
+
* @example
|
|
605
|
+
* ```ts
|
|
606
|
+
* const list = [1, 10, 5];
|
|
607
|
+
* arrayCompete(list, (a, b) => (a > b ? a : b)); // 10
|
|
608
|
+
* arrayCompete(list, (a, b) => (a < b ? a : b)); // 1
|
|
609
|
+
* ```
|
|
610
|
+
*/
|
|
611
|
+
function arrayCompete(initialList, match) {
|
|
612
|
+
if (!isArray(initialList) || initialList.length === 0 || !isFunction(match)) return null;
|
|
613
|
+
return initialList.reduce(match);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/utils/array/arrayCounting.ts
|
|
618
|
+
/**
|
|
619
|
+
* 统计数组的项目出现次数
|
|
620
|
+
* - 通过给定的标识符匹配函数,返回一个对象,其中键是回调函数返回的 key 值,每个值是一个整数,表示该 key 出现的次数
|
|
621
|
+
*
|
|
622
|
+
* @param initialList 初始数组
|
|
623
|
+
* @param match 匹配函数
|
|
624
|
+
* @returns 统计对象
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* const list = ["a", "b", "a", "c"];
|
|
628
|
+
* arrayCounting(list, (x) => x); // { a: 2, b: 1, c: 1 }
|
|
629
|
+
*
|
|
630
|
+
* const users = [{ id: 1, group: "A" }, { id: 2, group: "B" }, { id: 3, group: "A" }];
|
|
631
|
+
* arrayCounting(users, (u) => u.group); // { A: 2, B: 1 }
|
|
632
|
+
* ```
|
|
633
|
+
*/
|
|
634
|
+
function arrayCounting(initialList, match) {
|
|
635
|
+
if (!isArray(initialList) || !isFunction(match)) return {};
|
|
636
|
+
return initialList.reduce((prev, curr) => {
|
|
637
|
+
const id = match(curr).toString();
|
|
638
|
+
prev[id] = (prev[id] ?? 0) + 1;
|
|
639
|
+
return prev;
|
|
640
|
+
}, {});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/utils/array/arrayDifference.ts
|
|
645
|
+
/**
|
|
646
|
+
* 求数组差集
|
|
647
|
+
* - 返回在 `initialList` 中存在,但在 `diffList` 中不存在的元素
|
|
648
|
+
*
|
|
649
|
+
* @param initialList 初始数组
|
|
650
|
+
* @param diffList 对比数组
|
|
651
|
+
* @param match 匹配函数
|
|
652
|
+
* @returns 差集数组
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* arrayDifference([1, 2, 3], [2, 3, 4]); // [1]
|
|
656
|
+
* arrayDifference([{ id: 1 }, { id: 2 }], [{ id: 2 }], (x) => x.id); // [{ id: 1 }]
|
|
657
|
+
* ```
|
|
658
|
+
*/
|
|
659
|
+
function arrayDifference(initialList, diffList, match) {
|
|
660
|
+
if (!isArray(initialList) && !isArray(diffList)) return [];
|
|
661
|
+
if (!isArray(initialList) || !initialList.length) return [];
|
|
662
|
+
if (!isArray(diffList) || !diffList.length) return [...initialList];
|
|
663
|
+
if (!isFunction(match)) {
|
|
664
|
+
const arraySet = new Set(diffList);
|
|
665
|
+
return Array.from(new Set(initialList.filter((item) => !arraySet.has(item))));
|
|
666
|
+
}
|
|
667
|
+
const map = /* @__PURE__ */ new Map();
|
|
668
|
+
diffList.forEach((item) => {
|
|
669
|
+
map.set(match(item), true);
|
|
670
|
+
});
|
|
671
|
+
return initialList.filter((a) => !map.get(match(a)));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/utils/array/arrayFirst.ts
|
|
676
|
+
function arrayFirst(initialList, saveValue) {
|
|
677
|
+
if (!isArray(initialList) || initialList.length === 0) return saveValue;
|
|
678
|
+
return initialList[0];
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/utils/array/arrayFork.ts
|
|
683
|
+
/**
|
|
684
|
+
* 数组分组过滤
|
|
685
|
+
* - 给定一个数组和一个条件,返回一个由两个数组组成的元组,其中第一个数组包含所有满足条件的项,第二个数组包含所有不满足条件的项
|
|
686
|
+
*
|
|
687
|
+
* @param initialList 初始数组
|
|
688
|
+
* @param match 条件匹配函数
|
|
689
|
+
* @returns [满足条件的项[], 不满足条件的项[]]
|
|
690
|
+
* @example
|
|
691
|
+
* ```ts
|
|
692
|
+
* arrayFork([1, 2, 3, 4], (n) => n % 2 === 0); // [[2, 4], [1, 3]]
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
function arrayFork(initialList, match) {
|
|
696
|
+
const forked = [[], []];
|
|
697
|
+
if (isArray(initialList)) for (const item of initialList) forked[match(item) ? 0 : 1].push(item);
|
|
698
|
+
return forked;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/utils/array/arrayIntersection.ts
|
|
703
|
+
/**
|
|
704
|
+
* 求数组交集
|
|
705
|
+
* - 返回在 `initialList` 和 `diffList` 中都存在的元素
|
|
706
|
+
*
|
|
707
|
+
* @param initialList 初始数组
|
|
708
|
+
* @param diffList 对比数组
|
|
709
|
+
* @param match 匹配函数
|
|
710
|
+
* @returns 交集数组
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* arrayIntersection([1, 2], [2, 3]); // [2]
|
|
714
|
+
* arrayIntersection([{ id: 1 }, { id: 2 }], [{ id: 2 }], (x) => x.id); // [{ id: 2 }]
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
function arrayIntersection(initialList, diffList, match) {
|
|
718
|
+
if (!isArray(initialList) || !isArray(diffList)) return [];
|
|
719
|
+
if (!initialList.length || !diffList.length) return [];
|
|
720
|
+
if (!isFunction(match)) {
|
|
721
|
+
const diffSet = new Set(diffList);
|
|
722
|
+
return initialList.filter((item) => diffSet.has(item));
|
|
723
|
+
}
|
|
724
|
+
const diffKeys = new Set(diffList.map((item) => match(item)));
|
|
725
|
+
return initialList.filter((item) => diffKeys.has(match(item)));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/utils/array/arrayLast.ts
|
|
730
|
+
function arrayLast(initialList, saveValue) {
|
|
731
|
+
if (!isArray(initialList) || initialList.length === 0) return saveValue;
|
|
732
|
+
return initialList[initialList.length - 1];
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
//#endregion
|
|
736
|
+
//#region src/utils/array/arrayMerge.ts
|
|
737
|
+
/**
|
|
738
|
+
* 数组合并
|
|
739
|
+
* - 如果未提供 `match` 函数,则合并两个数组并去重(Union)
|
|
740
|
+
* - 如果提供了 `match` 函数,则仅更新 `initialList` 中匹配到的项(Left Join Update),不会追加 `mergeList` 中新增的项
|
|
741
|
+
*
|
|
742
|
+
* @param initialList 初始数组
|
|
743
|
+
* @param mergeList 待合并数组
|
|
744
|
+
* @param match 匹配函数
|
|
745
|
+
* @returns 合并后的数组
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* // 基础合并去重
|
|
749
|
+
* arrayMerge([1, 2], [2, 3]); // [1, 2, 3]
|
|
750
|
+
*
|
|
751
|
+
* // 按条件更新
|
|
752
|
+
* const source = [{ id: 1, val: "a" }, { id: 2, val: "b" }];
|
|
753
|
+
* const update = [{ id: 2, val: "new" }, { id: 3, val: "c" }];
|
|
754
|
+
* arrayMerge(source, update, (x) => x.id);
|
|
755
|
+
* // [{ id: 1, val: "a" }, { id: 2, val: "new" }] -> id:3 被忽略
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
function arrayMerge(initialList, mergeList, match) {
|
|
759
|
+
if (!isArray(initialList)) return [];
|
|
760
|
+
if (!isArray(mergeList)) return [...initialList];
|
|
761
|
+
if (!isFunction(match)) return Array.from(new Set([...initialList, ...mergeList]));
|
|
762
|
+
const keys = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const item of mergeList) keys.set(match(item), item);
|
|
764
|
+
return initialList.map((prevItem) => {
|
|
765
|
+
const key = match(prevItem);
|
|
766
|
+
return keys.has(key) ? keys.get(key) : prevItem;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region src/utils/array/arrayPick.ts
|
|
772
|
+
function arrayPick(initialList, filter, mapper) {
|
|
773
|
+
if (!isArray(initialList)) return [];
|
|
774
|
+
if (!isFunction(filter)) return [...initialList];
|
|
775
|
+
const hasMapper = isFunction(mapper);
|
|
776
|
+
return initialList.reduce((prev, curr, index) => {
|
|
777
|
+
if (!filter(curr, index)) return prev;
|
|
778
|
+
if (hasMapper) prev.push(mapper(curr, index));
|
|
779
|
+
else prev.push(curr);
|
|
780
|
+
return prev;
|
|
781
|
+
}, []);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
//#endregion
|
|
785
|
+
//#region src/utils/array/arrayReplace.ts
|
|
786
|
+
/**
|
|
787
|
+
* 数组项替换
|
|
788
|
+
* - 在给定的数组中,替换符合匹配函数结果的项目。只替换第一个匹配项。始终返回原始数组的副本。
|
|
789
|
+
*
|
|
790
|
+
* @param initialList 初始数组
|
|
791
|
+
* @param newItem 替换项
|
|
792
|
+
* @param match 匹配函数
|
|
793
|
+
* @returns 替换后的新数组
|
|
794
|
+
* @example
|
|
795
|
+
* ```ts
|
|
796
|
+
* arrayReplace([1, 2, 3], 4, (n) => n === 2); // [1, 4, 3]
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
function arrayReplace(initialList, newItem, match) {
|
|
800
|
+
if (!initialList) return [];
|
|
801
|
+
if (newItem === void 0 || !isFunction(match)) return [...initialList];
|
|
802
|
+
for (let i = 0; i < initialList.length; i++) {
|
|
803
|
+
const item = initialList[i];
|
|
804
|
+
if (item !== void 0 && match(item, i)) return [
|
|
805
|
+
...initialList.slice(0, i),
|
|
806
|
+
newItem,
|
|
807
|
+
...initialList.slice(i + 1, initialList.length)
|
|
808
|
+
];
|
|
809
|
+
}
|
|
810
|
+
return [...initialList];
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region src/utils/array/arraySplit.ts
|
|
815
|
+
/**
|
|
816
|
+
* 数组切分
|
|
817
|
+
* - 将数组以指定的长度切分后,组合在高维数组中
|
|
818
|
+
*
|
|
819
|
+
* @param initialList 初始数组
|
|
820
|
+
* @param size 分割尺寸,默认 `10`
|
|
821
|
+
* @returns 切分后的二维数组
|
|
822
|
+
* @example
|
|
823
|
+
* ```ts
|
|
824
|
+
* arraySplit([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
function arraySplit(initialList, size = 10) {
|
|
828
|
+
if (!isArray(initialList)) return [];
|
|
829
|
+
const count = Math.ceil(initialList.length / size);
|
|
830
|
+
return Array.from({ length: count }).fill(null).map((_c, i) => {
|
|
831
|
+
return initialList.slice(i * size, i * size + size);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
//#endregion
|
|
836
|
+
//#region src/utils/array/arrayZip.ts
|
|
837
|
+
/**
|
|
838
|
+
* 数组解压
|
|
839
|
+
* - `arrayZip` 的反向操作
|
|
840
|
+
*
|
|
841
|
+
* @param arrays 压缩后的数组
|
|
842
|
+
* @returns 解压后的二维数组
|
|
843
|
+
* @example
|
|
844
|
+
* ```ts
|
|
845
|
+
* arrayUnzip([[1, "a"], [2, "b"]]); // [[1, 2], ["a", "b"]]
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
function arrayUnzip(arrays) {
|
|
849
|
+
if (!isArray(arrays) || !arrays.length) return [];
|
|
850
|
+
const out = new Array(arrays.reduce((max, arr) => Math.max(max, arr.length), 0));
|
|
851
|
+
let index = 0;
|
|
852
|
+
const get = (array) => array[index];
|
|
853
|
+
for (; index < out.length; index++) out[index] = Array.from(arrays, get);
|
|
854
|
+
return out;
|
|
855
|
+
}
|
|
856
|
+
function arrayZip(...arrays) {
|
|
857
|
+
return arrayUnzip(arrays);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/utils/array/arrayZipToObject.ts
|
|
862
|
+
function arrayZipToObject(keys, values) {
|
|
863
|
+
const result = {};
|
|
864
|
+
if (!isArray(keys) || !keys.length) return result;
|
|
865
|
+
const getValue = isFunction(values) ? values : isArray(values) ? (_k, i) => values[i] : (_k, _i) => values;
|
|
866
|
+
return keys.reduce((acc, key, idx) => {
|
|
867
|
+
acc[key] = getValue(key, idx);
|
|
868
|
+
return acc;
|
|
869
|
+
}, result);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/utils/device/isMobile.ts
|
|
874
|
+
/**
|
|
875
|
+
* 检测当前设备是否为移动设备
|
|
876
|
+
*
|
|
877
|
+
* @param maxWidth - 移动设备最大宽度(默认 768px)
|
|
878
|
+
* @param dpi - 标准 DPI 基准(默认 160)
|
|
879
|
+
* @returns 是否为移动设备
|
|
880
|
+
* @example
|
|
881
|
+
* ```ts
|
|
882
|
+
* // 假设 window.innerWidth = 500
|
|
883
|
+
* isMobile(); // true
|
|
884
|
+
* ```
|
|
885
|
+
*/
|
|
886
|
+
function isMobile(maxWidth = 768, dpi = 160) {
|
|
887
|
+
if (typeof window === "undefined" || !isPositiveInteger(maxWidth)) return false;
|
|
888
|
+
return !isTablet(maxWidth, 1200, dpi);
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* 检测当前设备是否为IOS移动设备
|
|
892
|
+
*
|
|
893
|
+
* @param maxWidth - 移动设备最大宽度(默认 768px)
|
|
894
|
+
* @param dpi - 标准 DPI 基准(默认 160)
|
|
895
|
+
* @returns 是否为 iOS 移动设备 (iPhone/iPad/iPod 且非平板)
|
|
896
|
+
* @example
|
|
897
|
+
* ```ts
|
|
898
|
+
* // UA contains iPhone
|
|
899
|
+
* isIOSMobile(); // true
|
|
900
|
+
* ```
|
|
901
|
+
*/
|
|
902
|
+
function isIOSMobile(maxWidth = 768, dpi = 160) {
|
|
903
|
+
if (typeof navigator === "undefined" || !navigator.userAgent || !isPositiveInteger(maxWidth)) return false;
|
|
904
|
+
return /iPhone|iPad|iPod/i.test(navigator.userAgent) && !isTablet(maxWidth, 1200, dpi);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
//#endregion
|
|
908
|
+
//#region src/utils/device/isTablet.ts
|
|
909
|
+
/**
|
|
910
|
+
* 检测当前设备是否为平板
|
|
911
|
+
*
|
|
912
|
+
* @param minWidth - 平板最小宽度(默认 768px)
|
|
913
|
+
* @param maxWidth - 平板最大宽度(默认 1200px)
|
|
914
|
+
* @param dpi - 标准 DPI 基准(默认 160)
|
|
915
|
+
* @returns 是否为平板设备
|
|
916
|
+
* @example
|
|
917
|
+
* ```ts
|
|
918
|
+
* // 假设 window.innerWidth = 1000
|
|
919
|
+
* isTablet(); // true
|
|
920
|
+
* ```
|
|
921
|
+
*/
|
|
922
|
+
function isTablet(minWidth = 768, maxWidth = 1200, dpi = 160) {
|
|
923
|
+
if (typeof window === "undefined" || !isPositiveInteger(minWidth) || !isPositiveInteger(maxWidth)) return false;
|
|
924
|
+
const width = window.innerWidth;
|
|
925
|
+
const isWithinWidthRange = width >= minWidth && width <= maxWidth;
|
|
926
|
+
try {
|
|
927
|
+
const widthPx = window.screen.width;
|
|
928
|
+
const heightPx = window.screen.height;
|
|
929
|
+
const DPI = dpi * (window.devicePixelRatio || 1);
|
|
930
|
+
const widthInch = widthPx / DPI;
|
|
931
|
+
const heightInch = heightPx / DPI;
|
|
932
|
+
const screenInches = Math.sqrt(widthInch ** 2 + heightInch ** 2);
|
|
933
|
+
return isWithinWidthRange || screenInches >= 7;
|
|
934
|
+
} catch {
|
|
935
|
+
return isWithinWidthRange;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
//#endregion
|
|
940
|
+
//#region src/utils/function/to.ts
|
|
941
|
+
/**
|
|
942
|
+
*将 Promise 转换为 `[err, result]` 格式,方便 async/await 错误处理
|
|
943
|
+
*
|
|
944
|
+
* @param promise 待处理的 Promise
|
|
945
|
+
* @param errorExt 附加到 error 对象的扩展信息(注意:如果原 error 是 Error 实例,扩展属性可能会覆盖或无法正确合并非枚举属性)
|
|
946
|
+
* @returns `[err, null]` 或 `[null, data]`
|
|
947
|
+
* @example
|
|
948
|
+
* ```ts
|
|
949
|
+
* const [err, data] = await to(someAsyncFunc());
|
|
950
|
+
* if (err) return;
|
|
951
|
+
* console.log(data);
|
|
952
|
+
* ```
|
|
953
|
+
*/
|
|
954
|
+
function to(promise, errorExt) {
|
|
955
|
+
return promise.then((data) => [null, data]).catch((err) => {
|
|
956
|
+
if (errorExt) {
|
|
957
|
+
const parsedError = {
|
|
958
|
+
...err,
|
|
959
|
+
...errorExt
|
|
960
|
+
};
|
|
961
|
+
if (err instanceof Error) {
|
|
962
|
+
parsedError["message"] = err.message;
|
|
963
|
+
parsedError["stack"] = err.stack;
|
|
964
|
+
}
|
|
965
|
+
return [parsedError, void 0];
|
|
966
|
+
}
|
|
967
|
+
return [err ? err : /* @__PURE__ */ new Error("defaultError"), void 0];
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region src/utils/string/stringToNumber.ts
|
|
973
|
+
const R1$2 = /[^0-9.-]/g;
|
|
974
|
+
/**
|
|
975
|
+
* 从字符串中提取数字字符串
|
|
976
|
+
*
|
|
977
|
+
/**
|
|
978
|
+
* 从字符串中提取数字字符串
|
|
979
|
+
* - 移除非数字字符,保留符号和小数点
|
|
980
|
+
*
|
|
981
|
+
* @param input 待处理字符串
|
|
982
|
+
* @returns 提取出的数字字符串
|
|
983
|
+
* @example
|
|
984
|
+
* ```ts
|
|
985
|
+
* stringToNumber("$1,234.56"); // "1234.56"
|
|
986
|
+
* stringToNumber("abc-123"); // "-123"
|
|
987
|
+
* ```
|
|
988
|
+
*/
|
|
989
|
+
function stringToNumber(input) {
|
|
990
|
+
if (!isString(input, true)) return "";
|
|
991
|
+
const cleaned = input.replace(R1$2, "");
|
|
992
|
+
let isDecimal = false;
|
|
993
|
+
let signCount = 0;
|
|
994
|
+
let firstIndex = -1;
|
|
995
|
+
const stringList = cleaned.split("").map((s, i) => {
|
|
996
|
+
if (s === ".") {
|
|
997
|
+
if (isDecimal) return "";
|
|
998
|
+
isDecimal = true;
|
|
999
|
+
return ".";
|
|
1000
|
+
}
|
|
1001
|
+
if (s === "-") {
|
|
1002
|
+
firstIndex === -1 && signCount++;
|
|
1003
|
+
return "";
|
|
1004
|
+
}
|
|
1005
|
+
firstIndex === -1 && (firstIndex = i);
|
|
1006
|
+
return s;
|
|
1007
|
+
});
|
|
1008
|
+
const sign = signCount % 2 === 1 ? "-" : "";
|
|
1009
|
+
if (firstIndex === -1) return sign + "0";
|
|
1010
|
+
let result = stringList.join("");
|
|
1011
|
+
if (result.startsWith(".")) result = "0" + result;
|
|
1012
|
+
if (result.endsWith(".")) result = result.slice(0, -1);
|
|
1013
|
+
return sign + result;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region src/utils/math/toMathBignumber.ts
|
|
1018
|
+
/**
|
|
1019
|
+
* 将任意类型的值转换为 `math.bignumber`
|
|
1020
|
+
*
|
|
1021
|
+
* @param mathJsInstance mathJs 实例
|
|
1022
|
+
* @param value 任意类型的值
|
|
1023
|
+
* @param saveValue 安全值
|
|
1024
|
+
* @returns 转换后的 BigNumber
|
|
1025
|
+
* @example
|
|
1026
|
+
* ```ts
|
|
1027
|
+
* import { create, all } from "mathjs";
|
|
1028
|
+
* const math = create(all);
|
|
1029
|
+
* toMathBignumber(math, "0.1");
|
|
1030
|
+
* ```
|
|
1031
|
+
*/
|
|
1032
|
+
function toMathBignumber(mathJsInstance, value, saveValue) {
|
|
1033
|
+
const errorValue = saveValue ?? mathJsInstance.bignumber(0);
|
|
1034
|
+
if (isFalsyLike(value) || isInfinityLike(value)) return errorValue;
|
|
1035
|
+
try {
|
|
1036
|
+
return mathJsInstance.bignumber(stringToNumber(`${value}`));
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
return errorValue;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
//#endregion
|
|
1043
|
+
//#region src/utils/math/toMathDecimal.ts
|
|
1044
|
+
function toMathDecimal(mathJsInstance, value, precision, isFormat = true) {
|
|
1045
|
+
const bigNumber = toMathBignumber(mathJsInstance, value);
|
|
1046
|
+
return isFormat ? mathJsInstance.format(bigNumber, {
|
|
1047
|
+
notation: "fixed",
|
|
1048
|
+
precision
|
|
1049
|
+
}) : bigNumber;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region src/utils/math/toMathEvaluate.ts
|
|
1054
|
+
/**
|
|
1055
|
+
* 数学表达式求值
|
|
1056
|
+
*
|
|
1057
|
+
* @param mathJsInstance mathJs 实例
|
|
1058
|
+
* @param expr 表达式
|
|
1059
|
+
* @param scope 键值映射
|
|
1060
|
+
* @returns 计算结果的字符串表示
|
|
1061
|
+
* @example
|
|
1062
|
+
* ```ts
|
|
1063
|
+
* toMathEvaluate(math, "a + b", { a: 1, b: 2 }); // "3"
|
|
1064
|
+
* ```
|
|
1065
|
+
*/
|
|
1066
|
+
function toMathEvaluate(mathJsInstance, expr, scope) {
|
|
1067
|
+
const evaluateValue = `${mathJsInstance.evaluate(expr, scope || {})}`;
|
|
1068
|
+
return mathJsInstance.format(toMathBignumber(mathJsInstance, evaluateValue), { notation: "fixed" });
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
//#endregion
|
|
1072
|
+
//#region src/utils/number/isWithinInterval.ts
|
|
1073
|
+
/**
|
|
1074
|
+
* 数字区间检查函数
|
|
1075
|
+
*
|
|
1076
|
+
* @param input 待检查数字
|
|
1077
|
+
* @param interval 由两个数字组成的元组 [left, right]
|
|
1078
|
+
* @param includeLeft 是否包含左边界(默认 true)
|
|
1079
|
+
* @param includeRight 是否包含右边界(默认 false)
|
|
1080
|
+
* @returns 是否在区间内
|
|
1081
|
+
* @example
|
|
1082
|
+
* ```ts
|
|
1083
|
+
* isWithinInterval(5, [1, 10]); // true
|
|
1084
|
+
* isWithinInterval(1, [1, 10], false); // false
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
function isWithinInterval(input, interval, includeLeft = true, includeRight = false) {
|
|
1088
|
+
if (!isNumber(input)) throw new Error("params [input] mast be a number.");
|
|
1089
|
+
if (isInfinity(input)) throw new Error("params [input] mast be a finite number.");
|
|
1090
|
+
const [left, right] = interval;
|
|
1091
|
+
if (left > right) throw new Error(`Invalid interval: left (${left}) must be <= right (${right}).`);
|
|
1092
|
+
if (includeLeft && includeRight) return input >= left && input <= right;
|
|
1093
|
+
else if (includeLeft) return input >= left && input < right;
|
|
1094
|
+
else if (includeRight) return input > left && input <= right;
|
|
1095
|
+
else return input > left && input < right;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
//#endregion
|
|
1099
|
+
//#region src/utils/object/cloneDeep.ts
|
|
1100
|
+
const defaultCloneStrategy = {
|
|
1101
|
+
cloneMap,
|
|
1102
|
+
cloneSet,
|
|
1103
|
+
cloneDate,
|
|
1104
|
+
cloneArray,
|
|
1105
|
+
cloneObject,
|
|
1106
|
+
cloneOther
|
|
1107
|
+
};
|
|
1108
|
+
function cloneMap(input, track, clone) {
|
|
1109
|
+
const output = track(/* @__PURE__ */ new Map());
|
|
1110
|
+
for (const [key, value] of input) output.set(key, clone(value));
|
|
1111
|
+
return output;
|
|
1112
|
+
}
|
|
1113
|
+
function cloneSet(input, track, clone) {
|
|
1114
|
+
const output = track(/* @__PURE__ */ new Set());
|
|
1115
|
+
for (const value of input) output.add(clone(value));
|
|
1116
|
+
return output;
|
|
1117
|
+
}
|
|
1118
|
+
function cloneDate(input, track) {
|
|
1119
|
+
return track(new Date(input));
|
|
1120
|
+
}
|
|
1121
|
+
function cloneArray(input, track, clone) {
|
|
1122
|
+
const output = track(new Array(input.length));
|
|
1123
|
+
input.forEach((value, index) => {
|
|
1124
|
+
output[index] = clone(value);
|
|
1125
|
+
});
|
|
1126
|
+
return output;
|
|
1127
|
+
}
|
|
1128
|
+
function cloneObject(input, track, clone) {
|
|
1129
|
+
const output = track(Object.create(Object.getPrototypeOf(input)));
|
|
1130
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
1131
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
1132
|
+
if ("value" in descriptor) descriptor.value = clone(descriptor.value);
|
|
1133
|
+
Object.defineProperty(output, key, descriptor);
|
|
1134
|
+
}
|
|
1135
|
+
return output;
|
|
1136
|
+
}
|
|
1137
|
+
function cloneOther(input, track) {
|
|
1138
|
+
return track(input);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* 深度拷贝对象
|
|
1142
|
+
* - 支持 Array, Object, Map, Set
|
|
1143
|
+
* - 自动处理循环引用
|
|
1144
|
+
*
|
|
1145
|
+
* @param root 需要拷贝的对象
|
|
1146
|
+
* @param customStrategy 自定义拷贝策略
|
|
1147
|
+
* @returns 拷贝后的对象
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* const original = { a: 1, b: { c: 2 } };
|
|
1151
|
+
* const copy = cloneDeep(original);
|
|
1152
|
+
* copy.b.c = 3;
|
|
1153
|
+
* // original.b.c === 2
|
|
1154
|
+
* ```
|
|
1155
|
+
* @reference https://github.com/radashi-org/radashi/blob/main/src/object/cloneDeep.ts
|
|
1156
|
+
*/
|
|
1157
|
+
function cloneDeep(root, customStrategy) {
|
|
1158
|
+
const strategy = {
|
|
1159
|
+
...defaultCloneStrategy,
|
|
1160
|
+
...customStrategy
|
|
1161
|
+
};
|
|
1162
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
1163
|
+
const track = (parent, newParent) => {
|
|
1164
|
+
tracked.set(parent, newParent);
|
|
1165
|
+
return newParent;
|
|
1166
|
+
};
|
|
1167
|
+
const clone = (value) => {
|
|
1168
|
+
return value && typeof value === "object" ? tracked.get(value) ?? cloneDeep$1(value, strategy) : value;
|
|
1169
|
+
};
|
|
1170
|
+
const cloneDeep$1 = (parent, strategy$1) => {
|
|
1171
|
+
const newParent = (isObject(parent) ? strategy$1.cloneObject : isArray(parent) ? strategy$1.cloneArray : isMap(parent) ? strategy$1.cloneMap : isSet(parent) ? strategy$1.cloneSet : isDate(parent, false) ? strategy$1.cloneDate : strategy$1.cloneOther)(parent, track.bind(null, parent), clone);
|
|
1172
|
+
if (!newParent) return cloneDeep$1(parent, defaultCloneStrategy);
|
|
1173
|
+
tracked.set(parent, newParent);
|
|
1174
|
+
return newParent;
|
|
1175
|
+
};
|
|
1176
|
+
return cloneDeep$1(root, strategy);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
//#endregion
|
|
1180
|
+
//#region src/utils/object/enumEntries.ts
|
|
1181
|
+
function enumEntries(enumeration) {
|
|
1182
|
+
const [isEnum, isTwoWayEnum] = isEnumeration(enumeration);
|
|
1183
|
+
if (!isEnum) throw Error("function enumEntries expected parameter is a enum, and requires at least one member");
|
|
1184
|
+
const entries = objectEntries(enumeration);
|
|
1185
|
+
if (isTwoWayEnum) return entries.splice(entries.length / 2, entries.length / 2);
|
|
1186
|
+
return entries;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/utils/object/enumKeys.ts
|
|
1191
|
+
function enumKeys(enumeration) {
|
|
1192
|
+
const [isEnum, isTwoWayEnum] = isEnumeration(enumeration);
|
|
1193
|
+
if (!isEnum) throw Error("function enumKeys expected parameter is a enum, and requires at least one member");
|
|
1194
|
+
const keys = objectKeys(enumeration);
|
|
1195
|
+
if (isTwoWayEnum) return keys.splice(keys.length / 2, keys.length / 2);
|
|
1196
|
+
return keys;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region src/utils/object/enumValues.ts
|
|
1201
|
+
function enumValues(enumeration) {
|
|
1202
|
+
const [isEnum, isTwoWayEnum] = isEnumeration(enumeration);
|
|
1203
|
+
if (!isEnum) throw Error("function enumValues expected parameter is a enum, and requires at least one member");
|
|
1204
|
+
const values = objectValues(enumeration);
|
|
1205
|
+
if (isTwoWayEnum) return values.splice(values.length / 2, values.length / 2);
|
|
1206
|
+
return values;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
//#endregion
|
|
1210
|
+
//#region src/utils/object/objectEntries.ts
|
|
1211
|
+
function objectEntries(value) {
|
|
1212
|
+
return Object.entries(value);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
//#endregion
|
|
1216
|
+
//#region src/utils/object/mapEntries.ts
|
|
1217
|
+
/**
|
|
1218
|
+
* 映射对象条目
|
|
1219
|
+
* - 将对象的键值对映射为新的键值对
|
|
1220
|
+
*
|
|
1221
|
+
* @param obj 对象
|
|
1222
|
+
* @param toEntry 映射函数
|
|
1223
|
+
* @returns 映射后的新对象
|
|
1224
|
+
* @example
|
|
1225
|
+
* ```ts
|
|
1226
|
+
* const obj = { a: 1, b: 2 };
|
|
1227
|
+
* mapEntries(obj, (k, v) => [k, v * 2]); // { a: 2, b: 4 }
|
|
1228
|
+
* ```
|
|
1229
|
+
*/
|
|
1230
|
+
function mapEntries(obj, toEntry) {
|
|
1231
|
+
const defaultResult = {};
|
|
1232
|
+
if (!obj) return defaultResult;
|
|
1233
|
+
return objectEntries(obj).reduce((acc, [key, value]) => {
|
|
1234
|
+
const [newKey, newValue] = toEntry(key, value);
|
|
1235
|
+
acc[newKey] = newValue;
|
|
1236
|
+
return acc;
|
|
1237
|
+
}, defaultResult);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
//#endregion
|
|
1241
|
+
//#region src/utils/object/objectAssign.ts
|
|
1242
|
+
function objectAssign(initial, override) {
|
|
1243
|
+
if (!isObject(initial) || !isObject(override)) return initial ?? override ?? {};
|
|
1244
|
+
const proto = Object.getPrototypeOf(initial);
|
|
1245
|
+
const assigned = proto ? { ...initial } : Object.assign(Object.create(proto), initial);
|
|
1246
|
+
for (const key of Object.keys(override)) assigned[key] = isObject(initial[key]) && isObject(override[key]) ? objectAssign(initial[key], override[key]) : override[key];
|
|
1247
|
+
return assigned;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/utils/object/objectCrush.ts
|
|
1252
|
+
function objectCrush(obj) {
|
|
1253
|
+
if (!obj) return {};
|
|
1254
|
+
function crushReducer(crushed, value, path) {
|
|
1255
|
+
if (isObject(value) || isArray(value)) for (const [prop, propValue] of Object.entries(value)) crushReducer(crushed, propValue, path ? `${path}.${prop}` : prop);
|
|
1256
|
+
else crushed[path] = value;
|
|
1257
|
+
return crushed;
|
|
1258
|
+
}
|
|
1259
|
+
return crushReducer({}, obj, "");
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/utils/object/objectInvert.ts
|
|
1264
|
+
function objectInvert(obj) {
|
|
1265
|
+
const result = {};
|
|
1266
|
+
if (!isObject(obj)) return result;
|
|
1267
|
+
for (const [k, v] of objectEntries(obj)) if (isString(v) || isNumber(v) || isSymbol(v)) result[v] = k;
|
|
1268
|
+
return result;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
//#endregion
|
|
1272
|
+
//#region src/utils/object/objectKeys.ts
|
|
1273
|
+
function objectKeys(value) {
|
|
1274
|
+
return Object.keys(value);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
//#endregion
|
|
1278
|
+
//#region src/utils/object/objectOmit.ts
|
|
1279
|
+
function objectOmit(obj, keys) {
|
|
1280
|
+
const result = {};
|
|
1281
|
+
if (!isObject(obj)) return result;
|
|
1282
|
+
if (!isArray(keys)) return obj;
|
|
1283
|
+
const keysToOmit = new Set(keys);
|
|
1284
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
1285
|
+
if (!keysToOmit.has(key)) acc[key] = obj[key];
|
|
1286
|
+
return acc;
|
|
1287
|
+
}, result);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
//#endregion
|
|
1291
|
+
//#region src/utils/object/objectPick.ts
|
|
1292
|
+
function objectPick(obj, keys) {
|
|
1293
|
+
const result = {};
|
|
1294
|
+
if (!isObject(obj)) return result;
|
|
1295
|
+
if (!isArray(keys)) return obj;
|
|
1296
|
+
return keys.reduce((acc, key) => {
|
|
1297
|
+
if (key in obj) acc[key] = obj[key];
|
|
1298
|
+
return acc;
|
|
1299
|
+
}, result);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
//#endregion
|
|
1303
|
+
//#region src/utils/object/objectValues.ts
|
|
1304
|
+
function objectValues(value) {
|
|
1305
|
+
return Object.values(value);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
//#endregion
|
|
1309
|
+
//#region src/utils/string/stringInitialCase.ts
|
|
1310
|
+
const R1$1 = /\S+/g;
|
|
1311
|
+
const R2 = /[^a-zA-Z\u00C0-\u017F]/;
|
|
1312
|
+
/**
|
|
1313
|
+
* 字符串首字母大小写
|
|
1314
|
+
* - 包含非西欧字母字符时,不处理
|
|
1315
|
+
* - 纯字母且全大写时,不处理
|
|
1316
|
+
* - 纯字母且非全大写时,首字母小写,其余保留
|
|
1317
|
+
* - 纯字母且非全大写时,首字母大写,其余保留
|
|
1318
|
+
*
|
|
1319
|
+
/**
|
|
1320
|
+
* 字符串首字母大小写
|
|
1321
|
+
* - 包含非西欧字母字符时,不处理
|
|
1322
|
+
* - 纯字母且全大写时,不处理
|
|
1323
|
+
* - 纯字母且非全大写时,首字母小写,其余保留
|
|
1324
|
+
* - 纯字母且非全大写时,首字母大写,其余保留
|
|
1325
|
+
*
|
|
1326
|
+
* @param input 待处理字符串
|
|
1327
|
+
* @param caseType 大小写类型
|
|
1328
|
+
* @returns 处理后的字符串
|
|
1329
|
+
* @example
|
|
1330
|
+
* ```ts
|
|
1331
|
+
* stringInitialCase("Hello", "lower"); // "hello"
|
|
1332
|
+
* stringInitialCase("hello", "upper"); // "Hello"
|
|
1333
|
+
* ```
|
|
1334
|
+
*/
|
|
1335
|
+
function stringInitialCase(input, caseType) {
|
|
1336
|
+
if (!isString(input, true)) return "";
|
|
1337
|
+
return input.replace(R1$1, (word) => {
|
|
1338
|
+
if (R2.test(word)) return word;
|
|
1339
|
+
if (word === word.toLocaleUpperCase()) return word;
|
|
1340
|
+
if (caseType === "lower" && word[0]) return word[0].toLocaleLowerCase() + word.slice(1);
|
|
1341
|
+
if (caseType === "upper" && word[0]) return word[0].toLocaleUpperCase() + word.slice(1);
|
|
1342
|
+
return word;
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region src/utils/string/stringReplace.ts
|
|
1348
|
+
/**
|
|
1349
|
+
* 字符串替换
|
|
1350
|
+
* - 替换第一个匹配项
|
|
1351
|
+
*
|
|
1352
|
+
/**
|
|
1353
|
+
* 字符串替换
|
|
1354
|
+
* - 替换第一个匹配项
|
|
1355
|
+
*
|
|
1356
|
+
* @param input 待处理字符串
|
|
1357
|
+
* @param search 匹配项
|
|
1358
|
+
* @param replacement 替换项
|
|
1359
|
+
* @returns 替换后的字符串
|
|
1360
|
+
* @example
|
|
1361
|
+
* ```ts
|
|
1362
|
+
* stringReplace("hello world", "world", "context"); // "hello context"
|
|
1363
|
+
* ```
|
|
1364
|
+
*/
|
|
1365
|
+
function stringReplace(input, search, replacement) {
|
|
1366
|
+
if (!isString(input, true)) return "";
|
|
1367
|
+
return input.replace(search, replacement);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
//#endregion
|
|
1371
|
+
//#region src/utils/string/stringTemplate.ts
|
|
1372
|
+
const R1 = /\{\{(.+?)\}\}/g;
|
|
1373
|
+
/**
|
|
1374
|
+
* 字符串模板替换
|
|
1375
|
+
*
|
|
1376
|
+
/**
|
|
1377
|
+
* 字符串模板替换
|
|
1378
|
+
*
|
|
1379
|
+
* @param input 待处理字符串
|
|
1380
|
+
* @param template 模板对象
|
|
1381
|
+
* @param regex 模板匹配正则 (默认: `\{\{(.+?)\}\}`)
|
|
1382
|
+
* @returns 替换后的字符串
|
|
1383
|
+
* @example
|
|
1384
|
+
* ```ts
|
|
1385
|
+
* stringTemplate("Hello {{name}}", { name: "World" }); // "Hello World"
|
|
1386
|
+
* ```
|
|
1387
|
+
*/
|
|
1388
|
+
function stringTemplate(input, template, regex = R1) {
|
|
1389
|
+
if (!isString(input, true)) return "";
|
|
1390
|
+
let result = "";
|
|
1391
|
+
let from = 0;
|
|
1392
|
+
let match;
|
|
1393
|
+
while (match = regex.exec(input)) {
|
|
1394
|
+
result += input.slice(from, match.index) + template[match[1]];
|
|
1395
|
+
from = regex.lastIndex;
|
|
1396
|
+
}
|
|
1397
|
+
return result + input.slice(from);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/utils/string/stringToJson.ts
|
|
1402
|
+
/**
|
|
1403
|
+
* 处理 JSON 字符串
|
|
1404
|
+
*
|
|
1405
|
+
/**
|
|
1406
|
+
* 处理 JSON 字符串
|
|
1407
|
+
*
|
|
1408
|
+
* @param input 待处理字符串
|
|
1409
|
+
* @param safeValue 安全值 (当解析失败或输入无效时返回)
|
|
1410
|
+
* @returns 解析后的对象 或 安全值
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```ts
|
|
1413
|
+
* stringToJson('{"a": 1}', {}); // { a: 1 }
|
|
1414
|
+
* stringToJson('invalid', {}); // {}
|
|
1415
|
+
* ```
|
|
1416
|
+
*/
|
|
1417
|
+
function stringToJson(input, safeValue) {
|
|
1418
|
+
if (!isString(input, true)) return safeValue;
|
|
1419
|
+
try {
|
|
1420
|
+
return JSON.parse(input);
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
return safeValue;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
//#endregion
|
|
1427
|
+
//#region src/utils/string/stringToPosix.ts
|
|
1428
|
+
/**
|
|
1429
|
+
* 将路径转换为 POSIX 风格
|
|
1430
|
+
*
|
|
1431
|
+
/**
|
|
1432
|
+
* 将路径转换为 POSIX 风格
|
|
1433
|
+
* - 统一使用正斜杠
|
|
1434
|
+
* - 处理 Windows 盘符
|
|
1435
|
+
*
|
|
1436
|
+
* @param input 待处理字符串
|
|
1437
|
+
* @param removeLeadingSlash 是否移除开头斜杠,默认为 `false`
|
|
1438
|
+
* @returns 转换后的路径
|
|
1439
|
+
* @example
|
|
1440
|
+
* ```ts
|
|
1441
|
+
* stringToPosix("C:\\Windows\\System32"); // "/Windows/System32"
|
|
1442
|
+
* ```
|
|
1443
|
+
*/
|
|
1444
|
+
function stringToPosix(input, removeLeadingSlash = false) {
|
|
1445
|
+
if (!isString(input, true)) return "";
|
|
1446
|
+
let normalized = input.replace(/^[A-Za-z]:[\\/]?/, "").replace(/^[\\/]+/, "/");
|
|
1447
|
+
normalized = normalized.replace(/\\/g, "/");
|
|
1448
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
1449
|
+
if (removeLeadingSlash && normalized.startsWith("/")) normalized = normalized.substring(1);
|
|
1450
|
+
return normalized;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/utils/string/stringToValues.ts
|
|
1455
|
+
function stringToValues(input, valueType = "number", splitSymbol = ",") {
|
|
1456
|
+
if (!isString(input, true)) return [];
|
|
1457
|
+
try {
|
|
1458
|
+
const values = input.split(splitSymbol);
|
|
1459
|
+
if (valueType === "number") return values.map((d) => Number(d));
|
|
1460
|
+
return values;
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
//#endregion
|
|
1467
|
+
//#region src/utils/string/stringTrim.ts
|
|
1468
|
+
/**
|
|
1469
|
+
* 从字符串中裁切掉所有的前缀和后缀字符
|
|
1470
|
+
*
|
|
1471
|
+
* @param input 待处理字符串
|
|
1472
|
+
* @param charsToTrim 裁切字符,默认为 `" "`
|
|
1473
|
+
* @returns 裁切后的字符串
|
|
1474
|
+
* @example
|
|
1475
|
+
* ```ts
|
|
1476
|
+
* stringTrim(" hello "); // "hello"
|
|
1477
|
+
* stringTrim("__hello__", "_"); // "hello"
|
|
1478
|
+
* ```
|
|
1479
|
+
*/
|
|
1480
|
+
function stringTrim(input, charsToTrim = " ") {
|
|
1481
|
+
if (!isString(input, true)) return "";
|
|
1482
|
+
const toTrim = charsToTrim.replace(/[\W]{1}/g, "\\$&");
|
|
1483
|
+
const regex = new RegExp(`^[${toTrim}]+|[${toTrim}]+$`, "g");
|
|
1484
|
+
return input.replace(regex, "");
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
//#endregion
|
|
1488
|
+
//#region src/utils/string/stringTruncate.ts
|
|
1489
|
+
/**
|
|
1490
|
+
/**
|
|
1491
|
+
* 截取字符串
|
|
1492
|
+
* - 支持自定义省略符,不会截断在汉字中间(因为JS字符串本身按字符处理)
|
|
1493
|
+
*
|
|
1494
|
+
* @param input 待处理字符串
|
|
1495
|
+
* @param maxLength 最大长度 (包含省略符)
|
|
1496
|
+
* @param ellipsis 省略符,默认为 `...`
|
|
1497
|
+
* @returns 截取后的字符串
|
|
1498
|
+
* @example
|
|
1499
|
+
* ```ts
|
|
1500
|
+
* stringTruncate("hello world", 8); // "hello..."
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
function stringTruncate(input, maxLength, ellipsis = "...") {
|
|
1504
|
+
if (!isString(input, true)) return "";
|
|
1505
|
+
if (!isPositiveInteger(maxLength)) return input;
|
|
1506
|
+
if (input.length <= maxLength) return input;
|
|
1507
|
+
const truncated = input.slice(0, maxLength - ellipsis.length);
|
|
1508
|
+
return truncated.length > 0 ? truncated + ellipsis : "";
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region src/utils/time/timeZone.ts
|
|
1513
|
+
/**
|
|
1514
|
+
* 获取当前时区信息
|
|
1515
|
+
*
|
|
1516
|
+
* @returns 时区信息对象 (UTC偏移和时区名称)
|
|
1517
|
+
* @example
|
|
1518
|
+
* ```ts
|
|
1519
|
+
* getTimeZone(); // { UTC: "UTC+8", timeZone: "Asia/Shanghai" }
|
|
1520
|
+
* ```
|
|
1521
|
+
*/
|
|
1522
|
+
function getTimeZone() {
|
|
1523
|
+
const hour = 0 - (/* @__PURE__ */ new Date()).getTimezoneOffset() / 60;
|
|
1524
|
+
return {
|
|
1525
|
+
UTC: "UTC" + (hour >= 0 ? "+" + hour : hour),
|
|
1526
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/utils/tree/types.ts
|
|
1532
|
+
function getFinalChildrenKey(tree, meta, options) {
|
|
1533
|
+
if (isFunction(options.getChildrenKey)) {
|
|
1534
|
+
const dynamicChildrenKey = options.getChildrenKey(tree, meta);
|
|
1535
|
+
if (dynamicChildrenKey && dynamicChildrenKey !== null) return dynamicChildrenKey;
|
|
1536
|
+
}
|
|
1537
|
+
return options.childrenKey;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/utils/tree/treeFilter.ts
|
|
1542
|
+
function preImpl$3(row, callback, options) {
|
|
1543
|
+
if (!callback(row, options)) return;
|
|
1544
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
1545
|
+
const children = row[finalChildrenKey];
|
|
1546
|
+
let newChildren;
|
|
1547
|
+
if (isArray(children)) {
|
|
1548
|
+
const nextLevelOptions = {
|
|
1549
|
+
...options,
|
|
1550
|
+
parents: [...options.parents, row],
|
|
1551
|
+
depth: options.depth + 1
|
|
1552
|
+
};
|
|
1553
|
+
newChildren = children.map((c) => preImpl$3(c, callback, nextLevelOptions)).filter((c) => !!c);
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
...row,
|
|
1557
|
+
[finalChildrenKey]: newChildren
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function postImpl$3(row, callback, options) {
|
|
1561
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
1562
|
+
const children = row[finalChildrenKey];
|
|
1563
|
+
let newChildren;
|
|
1564
|
+
if (isArray(children)) {
|
|
1565
|
+
const nextLevelOptions = {
|
|
1566
|
+
...options,
|
|
1567
|
+
parents: [...options.parents, row],
|
|
1568
|
+
depth: options.depth + 1
|
|
1569
|
+
};
|
|
1570
|
+
newChildren = children.map((c) => preImpl$3(c, callback, nextLevelOptions)).filter((c) => !!c);
|
|
1571
|
+
}
|
|
1572
|
+
if (!callback(row, options)) return;
|
|
1573
|
+
return {
|
|
1574
|
+
...row,
|
|
1575
|
+
[finalChildrenKey]: newChildren
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
function breadthImpl$3(row, callback, options) {
|
|
1579
|
+
const queue = [{
|
|
1580
|
+
queueRow: row,
|
|
1581
|
+
queueOptions: options
|
|
1582
|
+
}];
|
|
1583
|
+
const resultCache = /* @__PURE__ */ new WeakMap();
|
|
1584
|
+
const newNodeCache = /* @__PURE__ */ new WeakMap();
|
|
1585
|
+
const childrenKeyCache = /* @__PURE__ */ new WeakMap();
|
|
1586
|
+
let result;
|
|
1587
|
+
const runQueue = () => {
|
|
1588
|
+
if (queue.length === 0) return result;
|
|
1589
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
1590
|
+
const finalChildrenKey = getFinalChildrenKey(queueRow, queueOptions, queueOptions);
|
|
1591
|
+
const children = queueRow[finalChildrenKey];
|
|
1592
|
+
if (isArray(children)) {
|
|
1593
|
+
const nextLevelOptions = {
|
|
1594
|
+
...queueOptions,
|
|
1595
|
+
parents: [...queueOptions.parents, queueRow],
|
|
1596
|
+
depth: queueOptions.depth + 1
|
|
1597
|
+
};
|
|
1598
|
+
const subQueueItems = children.map((queueRow$1) => ({
|
|
1599
|
+
queueRow: queueRow$1,
|
|
1600
|
+
queueOptions: nextLevelOptions
|
|
1601
|
+
}));
|
|
1602
|
+
queue.push(...subQueueItems);
|
|
1603
|
+
}
|
|
1604
|
+
const parent = arrayLast(queueOptions.parents);
|
|
1605
|
+
const isTopNode = queueOptions.depth === 0;
|
|
1606
|
+
const parentResult = parent && resultCache.get(parent);
|
|
1607
|
+
if (!isTopNode && !parentResult) return runQueue();
|
|
1608
|
+
const callbackResult = callback(queueRow, queueOptions);
|
|
1609
|
+
if (isTopNode && !callbackResult) return;
|
|
1610
|
+
const newNode = {
|
|
1611
|
+
...queueRow,
|
|
1612
|
+
[finalChildrenKey]: void 0
|
|
1613
|
+
};
|
|
1614
|
+
if (isTopNode) result = newNode;
|
|
1615
|
+
resultCache.set(queueRow, callbackResult);
|
|
1616
|
+
newNodeCache.set(queueRow, newNode);
|
|
1617
|
+
childrenKeyCache.set(queueRow, finalChildrenKey);
|
|
1618
|
+
if (callbackResult && parent) {
|
|
1619
|
+
const parentNewNode = newNodeCache.get(parent);
|
|
1620
|
+
const parentChildrenKey = childrenKeyCache.get(parent);
|
|
1621
|
+
if (parentNewNode && parentChildrenKey) {
|
|
1622
|
+
if (!parentNewNode[parentChildrenKey]) parentNewNode[parentChildrenKey] = [];
|
|
1623
|
+
parentNewNode[parentChildrenKey].push(newNode);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return runQueue();
|
|
1627
|
+
};
|
|
1628
|
+
return runQueue();
|
|
1629
|
+
}
|
|
1630
|
+
const strategies$3 = {
|
|
1631
|
+
pre: preImpl$3,
|
|
1632
|
+
post: postImpl$3,
|
|
1633
|
+
breadth: breadthImpl$3
|
|
1634
|
+
};
|
|
1635
|
+
function treeFilter(tree, callback, options = {}) {
|
|
1636
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
1637
|
+
const traversalMethod = strategies$3[strategy];
|
|
1638
|
+
const innerOptions = {
|
|
1639
|
+
childrenKey,
|
|
1640
|
+
depth: 0,
|
|
1641
|
+
parents: [],
|
|
1642
|
+
getChildrenKey
|
|
1643
|
+
};
|
|
1644
|
+
return isArray(tree) ? tree.map((row) => traversalMethod(row, callback, innerOptions)).filter((t) => !!t) : traversalMethod(tree, callback, innerOptions) || [];
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
//#endregion
|
|
1648
|
+
//#region src/utils/tree/treeFind.ts
|
|
1649
|
+
const strategies$2 = {
|
|
1650
|
+
pre: preImpl$2,
|
|
1651
|
+
post: postImpl$2,
|
|
1652
|
+
breadth: breadthImpl$2
|
|
1653
|
+
};
|
|
1654
|
+
function preImpl$2(row, callback, options) {
|
|
1655
|
+
if (callback(row, options)) return row;
|
|
1656
|
+
const children = row[getFinalChildrenKey(row, options, options)];
|
|
1657
|
+
if (isArray(children)) for (const child of children) {
|
|
1658
|
+
const result = preImpl$2(child, callback, {
|
|
1659
|
+
...options,
|
|
1660
|
+
parents: [...options.parents, row],
|
|
1661
|
+
depth: options.depth + 1
|
|
1662
|
+
});
|
|
1663
|
+
if (result) return result;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function postImpl$2(row, callback, options) {
|
|
1667
|
+
const children = row[getFinalChildrenKey(row, options, options)];
|
|
1668
|
+
if (isArray(children)) for (const child of children) {
|
|
1669
|
+
const result = postImpl$2(child, callback, {
|
|
1670
|
+
...options,
|
|
1671
|
+
parents: [...options.parents, row],
|
|
1672
|
+
depth: options.depth + 1
|
|
1673
|
+
});
|
|
1674
|
+
if (result) return result;
|
|
1675
|
+
}
|
|
1676
|
+
if (callback(row, options)) return row;
|
|
1677
|
+
}
|
|
1678
|
+
function breadthImpl$2(row, callback, options) {
|
|
1679
|
+
const queue = [{
|
|
1680
|
+
queueRow: row,
|
|
1681
|
+
queueOptions: options
|
|
1682
|
+
}];
|
|
1683
|
+
const runQueue = () => {
|
|
1684
|
+
if (queue.length === 0) return;
|
|
1685
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
1686
|
+
const children = queueRow[getFinalChildrenKey(queueRow, queueOptions, queueOptions)];
|
|
1687
|
+
if (isArray(children)) {
|
|
1688
|
+
const nextLevelOptions = {
|
|
1689
|
+
...queueOptions,
|
|
1690
|
+
parents: [...queueOptions.parents, queueRow],
|
|
1691
|
+
depth: queueOptions.depth + 1
|
|
1692
|
+
};
|
|
1693
|
+
const subQueueItems = children.map((queueRow$1) => ({
|
|
1694
|
+
queueRow: queueRow$1,
|
|
1695
|
+
queueOptions: nextLevelOptions
|
|
1696
|
+
}));
|
|
1697
|
+
queue.push(...subQueueItems);
|
|
1698
|
+
}
|
|
1699
|
+
if (callback(queueRow, queueOptions)) return queueRow;
|
|
1700
|
+
return runQueue();
|
|
1701
|
+
};
|
|
1702
|
+
return runQueue();
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* 查找树节点
|
|
1706
|
+
* - 返回第一个回调返回 true 的节点
|
|
1707
|
+
*
|
|
1708
|
+
* @param tree 树结构数据
|
|
1709
|
+
* @param callback 回调函数
|
|
1710
|
+
* @param options 配置项
|
|
1711
|
+
* @returns 找到的节点,未找到则返回 undefined
|
|
1712
|
+
* @example
|
|
1713
|
+
* ```ts
|
|
1714
|
+
* const tree = [{ id: 1, children: [{ id: 2 }] }];
|
|
1715
|
+
* treeFind(tree, (node) => node.id === 2); // { id: 2, ... }
|
|
1716
|
+
* ```
|
|
1717
|
+
*/
|
|
1718
|
+
function treeFind(tree, callback, options = {}) {
|
|
1719
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
1720
|
+
const traversalMethod = strategies$2[strategy];
|
|
1721
|
+
const innerOptions = {
|
|
1722
|
+
childrenKey,
|
|
1723
|
+
depth: 0,
|
|
1724
|
+
parents: [],
|
|
1725
|
+
getChildrenKey
|
|
1726
|
+
};
|
|
1727
|
+
if (isArray(tree)) {
|
|
1728
|
+
for (const row of tree) {
|
|
1729
|
+
const result = traversalMethod(row, callback, innerOptions);
|
|
1730
|
+
if (result) return result;
|
|
1731
|
+
}
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
return traversalMethod(tree, callback, innerOptions);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
//#endregion
|
|
1738
|
+
//#region src/utils/tree/treeForEach.ts
|
|
1739
|
+
const strategies$1 = {
|
|
1740
|
+
pre: preImpl$1,
|
|
1741
|
+
post: postImpl$1,
|
|
1742
|
+
breadth: breadthImpl$1
|
|
1743
|
+
};
|
|
1744
|
+
function preImpl$1(row, callback, options) {
|
|
1745
|
+
callback(row, options);
|
|
1746
|
+
const children = row[getFinalChildrenKey(row, options, options)];
|
|
1747
|
+
if (isArray(children)) {
|
|
1748
|
+
const nextLevelOptions = {
|
|
1749
|
+
...options,
|
|
1750
|
+
parents: [...options.parents, row],
|
|
1751
|
+
depth: options.depth + 1
|
|
1752
|
+
};
|
|
1753
|
+
for (const child of children) preImpl$1(child, callback, nextLevelOptions);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
function postImpl$1(row, callback, options) {
|
|
1757
|
+
const children = row[getFinalChildrenKey(row, options, options)];
|
|
1758
|
+
if (isArray(children)) {
|
|
1759
|
+
const nextLevelOptions = {
|
|
1760
|
+
...options,
|
|
1761
|
+
parents: [...options.parents, row],
|
|
1762
|
+
depth: options.depth + 1
|
|
1763
|
+
};
|
|
1764
|
+
for (const child of children) postImpl$1(child, callback, nextLevelOptions);
|
|
1765
|
+
}
|
|
1766
|
+
callback(row, options);
|
|
1767
|
+
}
|
|
1768
|
+
function breadthImpl$1(row, callback, options) {
|
|
1769
|
+
const queue = [{
|
|
1770
|
+
queueRow: row,
|
|
1771
|
+
queueOptions: options
|
|
1772
|
+
}];
|
|
1773
|
+
const runQueue = () => {
|
|
1774
|
+
if (queue.length === 0) return;
|
|
1775
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
1776
|
+
const children = queueRow[getFinalChildrenKey(queueRow, queueOptions, queueOptions)];
|
|
1777
|
+
if (isArray(children)) {
|
|
1778
|
+
const nextLevelOptions = {
|
|
1779
|
+
...queueOptions,
|
|
1780
|
+
parents: [...queueOptions.parents, queueRow],
|
|
1781
|
+
depth: queueOptions.depth + 1
|
|
1782
|
+
};
|
|
1783
|
+
const subQueueItems = children.map((queueRow$1) => ({
|
|
1784
|
+
queueRow: queueRow$1,
|
|
1785
|
+
queueOptions: nextLevelOptions
|
|
1786
|
+
}));
|
|
1787
|
+
queue.push(...subQueueItems);
|
|
1788
|
+
}
|
|
1789
|
+
callback(queueRow, queueOptions);
|
|
1790
|
+
runQueue();
|
|
1791
|
+
};
|
|
1792
|
+
runQueue();
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* 遍历树节点
|
|
1796
|
+
*
|
|
1797
|
+
* @param tree 树结构数据
|
|
1798
|
+
* @param callback 回调函数
|
|
1799
|
+
* @param options 配置项
|
|
1800
|
+
* @example
|
|
1801
|
+
* ```ts
|
|
1802
|
+
* const tree = [{ id: 1, children: [{ id: 2 }] }];
|
|
1803
|
+
* const ids: number[] = [];
|
|
1804
|
+
* treeForEach(tree, (node) => ids.push(node.id));
|
|
1805
|
+
* // ids: [1, 2] (pre-order default)
|
|
1806
|
+
* ```
|
|
1807
|
+
*/
|
|
1808
|
+
function treeForEach(tree, callback, options = {}) {
|
|
1809
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
1810
|
+
const traversalMethod = strategies$1[strategy];
|
|
1811
|
+
const innerOptions = {
|
|
1812
|
+
childrenKey,
|
|
1813
|
+
depth: 0,
|
|
1814
|
+
parents: [],
|
|
1815
|
+
getChildrenKey
|
|
1816
|
+
};
|
|
1817
|
+
if (isArray(tree)) for (const row of tree) traversalMethod(row, callback, innerOptions);
|
|
1818
|
+
else traversalMethod(tree, callback, innerOptions);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/utils/tree/treeMap.ts
|
|
1823
|
+
const strategies = {
|
|
1824
|
+
pre: preImpl,
|
|
1825
|
+
post: postImpl,
|
|
1826
|
+
breadth: breadthImpl
|
|
1827
|
+
};
|
|
1828
|
+
function preImpl(row, callback, options) {
|
|
1829
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
1830
|
+
const result = callback(row, options);
|
|
1831
|
+
const children = row[finalChildrenKey];
|
|
1832
|
+
let newChildren;
|
|
1833
|
+
if (isArray(children)) {
|
|
1834
|
+
const nextLevelOptions = {
|
|
1835
|
+
...options,
|
|
1836
|
+
parents: [...options.parents, row],
|
|
1837
|
+
depth: options.depth + 1
|
|
1838
|
+
};
|
|
1839
|
+
newChildren = children.map((c) => preImpl(c, callback, nextLevelOptions));
|
|
1840
|
+
}
|
|
1841
|
+
return {
|
|
1842
|
+
...result,
|
|
1843
|
+
[finalChildrenKey]: newChildren
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function postImpl(row, callback, options) {
|
|
1847
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
1848
|
+
const children = row[finalChildrenKey];
|
|
1849
|
+
let newChildren;
|
|
1850
|
+
if (isArray(children)) {
|
|
1851
|
+
const nextLevelOptions = {
|
|
1852
|
+
...options,
|
|
1853
|
+
parents: [...options.parents, row],
|
|
1854
|
+
depth: options.depth + 1
|
|
1855
|
+
};
|
|
1856
|
+
newChildren = children.map((c) => postImpl(c, callback, nextLevelOptions));
|
|
1857
|
+
}
|
|
1858
|
+
return {
|
|
1859
|
+
...callback(row, options),
|
|
1860
|
+
[finalChildrenKey]: newChildren
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function breadthImpl(row, callback, options) {
|
|
1864
|
+
const queue = [{
|
|
1865
|
+
queueRow: row,
|
|
1866
|
+
queueOptions: options
|
|
1867
|
+
}];
|
|
1868
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
1869
|
+
const childrenKeyCache = /* @__PURE__ */ new WeakMap();
|
|
1870
|
+
let result;
|
|
1871
|
+
const runQueue = () => {
|
|
1872
|
+
if (queue.length === 0) return result;
|
|
1873
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
1874
|
+
const finalChildrenKey = getFinalChildrenKey(queueRow, queueOptions, queueOptions);
|
|
1875
|
+
const children = queueRow[finalChildrenKey];
|
|
1876
|
+
if (isArray(children)) {
|
|
1877
|
+
const nextLevelOptions = {
|
|
1878
|
+
...queueOptions,
|
|
1879
|
+
parents: [...queueOptions.parents, queueRow],
|
|
1880
|
+
depth: queueOptions.depth + 1
|
|
1881
|
+
};
|
|
1882
|
+
const subQueueItems = children.map((queueRow$1) => ({
|
|
1883
|
+
queueRow: queueRow$1,
|
|
1884
|
+
queueOptions: nextLevelOptions
|
|
1885
|
+
}));
|
|
1886
|
+
queue.push(...subQueueItems);
|
|
1887
|
+
}
|
|
1888
|
+
const res = callback(queueRow, queueOptions);
|
|
1889
|
+
cache.set(queueRow, res);
|
|
1890
|
+
childrenKeyCache.set(queueRow, finalChildrenKey);
|
|
1891
|
+
const parent = arrayLast(queueOptions.parents);
|
|
1892
|
+
if (parent) {
|
|
1893
|
+
const newParent = cache.get(parent);
|
|
1894
|
+
const parentChildrenKey = childrenKeyCache.get(parent);
|
|
1895
|
+
if (newParent && parentChildrenKey) if (newParent[parentChildrenKey]) newParent[parentChildrenKey].push(res);
|
|
1896
|
+
else newParent[parentChildrenKey] = [res];
|
|
1897
|
+
}
|
|
1898
|
+
if (queueOptions.depth === 0) result = res;
|
|
1899
|
+
return runQueue();
|
|
1900
|
+
};
|
|
1901
|
+
return runQueue();
|
|
1902
|
+
}
|
|
1903
|
+
function treeMap(tree, callback, options = {}) {
|
|
1904
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
1905
|
+
const traversalMethod = strategies[strategy];
|
|
1906
|
+
const innerOptions = {
|
|
1907
|
+
childrenKey,
|
|
1908
|
+
depth: 0,
|
|
1909
|
+
parents: [],
|
|
1910
|
+
getChildrenKey
|
|
1911
|
+
};
|
|
1912
|
+
return isArray(tree) ? tree.map((row) => traversalMethod(row, callback, innerOptions)) : traversalMethod(tree, callback, innerOptions);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
//#endregion
|
|
1916
|
+
//#region src/utils/tree/rowsToTree.ts
|
|
1917
|
+
/**
|
|
1918
|
+
* 行结构 转 树结构
|
|
1919
|
+
* - 将平铺的数组转换为树形结构
|
|
1920
|
+
*
|
|
1921
|
+
* @param rows 行数据数组
|
|
1922
|
+
* @param options 配置项
|
|
1923
|
+
* @returns 树结构数组
|
|
1924
|
+
* @example
|
|
1925
|
+
* ```ts
|
|
1926
|
+
* const rows = [
|
|
1927
|
+
* { id: 1, parentId: null },
|
|
1928
|
+
* { id: 2, parentId: 1 },
|
|
1929
|
+
* ];
|
|
1930
|
+
* rowsToTree(rows);
|
|
1931
|
+
* // [{ id: 1, parentId: null, children: [{ id: 2, parentId: 1 }] }]
|
|
1932
|
+
* ```
|
|
1933
|
+
*/
|
|
1934
|
+
function rowsToTree(rows, options) {
|
|
1935
|
+
const { parentIdKey = "parentId", rowKey = "id", childrenKey = "children" } = options || {};
|
|
1936
|
+
const result = [];
|
|
1937
|
+
const map = /* @__PURE__ */ new Map();
|
|
1938
|
+
for (const row of rows) {
|
|
1939
|
+
const id = row[rowKey];
|
|
1940
|
+
if (!map.get(id)) map.set(id, row);
|
|
1941
|
+
}
|
|
1942
|
+
for (const row of rows) {
|
|
1943
|
+
const parentId = row[parentIdKey];
|
|
1944
|
+
const parent = map.get(parentId);
|
|
1945
|
+
if (!parent || !parentId) {
|
|
1946
|
+
result.push(row);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const siblings = parent[childrenKey];
|
|
1950
|
+
if (isNull(siblings) || isUndefined(siblings)) parent[childrenKey] = [row];
|
|
1951
|
+
else if (Array.isArray(siblings)) siblings.push(row);
|
|
1952
|
+
else {
|
|
1953
|
+
const message = `The key "${childrenKey.toString()}" in parent item is not an array.`;
|
|
1954
|
+
throw new Error(message);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return result;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
//#endregion
|
|
1961
|
+
//#region src/utils/tree/treeToRows.ts
|
|
1962
|
+
/**
|
|
1963
|
+
* 树结构 转 行结构
|
|
1964
|
+
* - 将树形结构扁平化为数组
|
|
1965
|
+
*
|
|
1966
|
+
* @param tree 树结构数据 (单个节点或节点数组)
|
|
1967
|
+
* @param options 配置项
|
|
1968
|
+
* @returns 扁平化后的数组
|
|
1969
|
+
* @example
|
|
1970
|
+
* ```ts
|
|
1971
|
+
* const tree = [{ id: 1, children: [{ id: 2 }] }];
|
|
1972
|
+
* treeToRows(tree);
|
|
1973
|
+
* // [{ id: 1, children: undefined }, { id: 2, children: undefined }]
|
|
1974
|
+
* ```
|
|
1975
|
+
*/
|
|
1976
|
+
function treeToRows(tree, options = {}) {
|
|
1977
|
+
const { childrenKey = "children" } = options;
|
|
1978
|
+
const result = [];
|
|
1979
|
+
if (!tree) return result;
|
|
1980
|
+
treeForEach(tree, (t) => result.push({
|
|
1981
|
+
...t,
|
|
1982
|
+
[childrenKey]: void 0
|
|
1983
|
+
}), options);
|
|
1984
|
+
return result;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
//#endregion
|
|
1988
|
+
export { isWindow as $, toMathEvaluate as A, isAsyncFunction as At, arrayZip as B, isAbortSignal as Bt, mapEntries as C, isFalsy as Ct, enumEntries as D, isEnumeration as Dt, enumKeys as E, isEqual as Et, isTablet as F, isBlob as Ft, arrayLast as G, arrayReplace as H, isIOSMobile as I, isFile as It, arrayFirst as J, arrayIntersection as K, isMobile as L, isBigInt as Lt, toMathBignumber as M, isFunction as Mt, stringToNumber as N, isGeneratorFunction as Nt, cloneDeep as O, isDate as Ot, to as P, isBoolean as Pt, arrayCast as Q, arrayZipToObject as R, isArray as Rt, objectAssign as S, isIterable as St, enumValues as T, isError as Tt, arrayPick as U, arraySplit as V, arrayMerge as W, arrayCounting as X, arrayDifference as Y, arrayCompete as Z, objectPick as _, isNumber as _t, treeFind as a, isWeakSet as at, objectInvert as b, isMap as bt, stringTruncate as c, isReadableStream as ct, stringToPosix as d, isObject as dt, isWebSocket as et, stringToJson as f, isInfinity as ft, objectValues as g, isNegativeInteger as gt, stringInitialCase as h, isNaN as ht, treeForEach as i, isSet as it, toMathDecimal as j, isAsyncGeneratorFunction as jt, isWithinInterval as k, isClass as kt, stringTrim as l, isPromise as lt, stringReplace as m, isInteger as mt, rowsToTree as n, isUndefined as nt, treeFilter as o, isRegExp as ot, stringTemplate as p, isInfinityLike as pt, arrayFork as q, treeMap as r, isSymbol as rt, getTimeZone as s, isString as st, treeToRows as t, isURLSearchParams as tt, stringToValues as u, isPromiseLike as ut, objectOmit as v, isPositiveInteger as vt, objectEntries as w, isFalsyLike as wt, objectCrush as x, isWeakMap as xt, objectKeys as y, isNull as yt, arrayUnzip as z, isTypedArray as zt };
|
|
1989
|
+
//# sourceMappingURL=utils-khUJYSj2.js.map
|