pen-it 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -3
- package/dist/array/index.d.ts +62 -0
- package/dist/browser/index.d.ts +15 -0
- package/dist/control/index.d.ts +36 -0
- package/dist/index.cjs +198 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +190 -1
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# pen-it
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
一个轻量级的前端工具函数库,涵盖类型判断、日期格式化、数字格式化、字符串处理、存储操作、深拷贝、数组操作、Cookie 操作、浏览器 API 等常用场景。
|
|
3
|
+
用笔锚定记忆中的前端常用工具函数,一个轻量级的前端实用程序库,支持类型检查、日期/数字格式化、字符串操作、数据存储、深度克隆、数组操作、Cookie 以及浏览器 API
|
|
6
4
|
|
|
7
5
|
## 安装
|
|
8
6
|
|
|
@@ -420,6 +418,45 @@ import { isArray, formatFull, deepClone, unique } from 'pen-it'
|
|
|
420
418
|
toArray(document.querySelectorAll('div'))
|
|
421
419
|
```
|
|
422
420
|
|
|
421
|
+
- **`mergeArrays(...arrays)`** — 合并多个数组。
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
mergeArrays([1, 2], [3, 4], [5]) // [1, 2, 3, 4, 5]
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
- **`flatten(arr, depth?)`** — 多维数组扁平化到指定层级(默认 1 层,传 `Infinity` 完全展开)。
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
flatten([1, [2, [3, 4]], 5]) // [1, 2, [3, 4], 5]
|
|
431
|
+
flatten([1, [2, [3, 4]], 5], Infinity) // [1, 2, 3, 4, 5]
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- **`arrFind(arr, key, value)`** — 对象数组中按 key-value 查找第一个匹配项。
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
arrFind([{ id: 1 }, { id: 2 }], 'id', 2) // { id: 2 }
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
- **`groupBy(arr, key)`** — 对象数组按指定属性分组。
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
groupBy([{ type: 'fruit' }, { type: 'vegetable' }], 'type')
|
|
444
|
+
// => { fruit: [...], vegetable: [...] }
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- **`filterEmptyValues(obj)`** — 移除对象中值为空(null / undefined / 空字符串)的属性。
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
filterEmptyValues({ a: 1, b: '', c: null }) // { a: 1 }
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
- **`createRange(length, mapFn?)`** — 快速生成范围数组。
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
createRange(5) // [0, 1, 2, 3, 4]
|
|
457
|
+
createRange(3, (i) => i * 2) // [0, 2, 4]
|
|
458
|
+
```
|
|
459
|
+
|
|
423
460
|
---
|
|
424
461
|
|
|
425
462
|
### Cookie — `cookie`
|
|
@@ -504,4 +541,35 @@ import { isArray, formatFull, deepClone, unique } from 'pen-it'
|
|
|
504
541
|
|
|
505
542
|
```ts
|
|
506
543
|
const off = onScroll(y => console.log(y))
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### 可视区域检测
|
|
547
|
+
|
|
548
|
+
- **`observeIntersection(target, onEnter, onLeave?, options?)`** — 监听元素进入/离开可视区域,返回清理函数。
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
const cleanup = observeIntersection(
|
|
552
|
+
document.querySelector('.footer')!,
|
|
553
|
+
() => loadMore(10),
|
|
554
|
+
);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
### 防抖与节流 — `control`
|
|
560
|
+
|
|
561
|
+
- **`debounce(fn, delay?, options?)`** — 防抖,停止调用 delay 毫秒后才执行。
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const fn = debounce((val: string) => console.log(val), 500)
|
|
565
|
+
fn('a'); fn('b'); fn('c')
|
|
566
|
+
// => 'c'
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
- **`throttle(fn, interval?, options?)`** — 节流,固定间隔内最多执行一次。
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
const fn = throttle((val: string) => console.log(val), 500)
|
|
573
|
+
fn('a'); fn('b'); fn('c')
|
|
574
|
+
// => 'a',500ms 后输出 'c'
|
|
507
575
|
```
|
package/dist/array/index.d.ts
CHANGED
|
@@ -55,3 +55,65 @@ export declare const sortByKey: <T extends Record<string, any>>(arr: T[], key: k
|
|
|
55
55
|
* // => [div, div, ...]
|
|
56
56
|
*/
|
|
57
57
|
export declare const toArray: <T>(arrayLike: ArrayLike<T>) => T[];
|
|
58
|
+
/**
|
|
59
|
+
* 合并多个数组
|
|
60
|
+
* @param arrays - 一个或多个数组
|
|
61
|
+
* @returns 合并后的新数组
|
|
62
|
+
* @example
|
|
63
|
+
* mergeArrays([1, 2], [3, 4], [5])
|
|
64
|
+
* // => [1, 2, 3, 4, 5]
|
|
65
|
+
*/
|
|
66
|
+
export declare const mergeArrays: <T>(...arrays: T[][]) => T[];
|
|
67
|
+
/**
|
|
68
|
+
* 将多维数组扁平化到指定层级
|
|
69
|
+
* @param arr - 多维数组
|
|
70
|
+
* @param depth - 扁平化深度,默认 1(仅展开一层)。传 Infinity 可完全扁平化为一维
|
|
71
|
+
* @returns 扁平化后的数组
|
|
72
|
+
* @example
|
|
73
|
+
* flatten([1, [2, [3, 4]], 5])
|
|
74
|
+
* // => [1, 2, [3, 4], 5]
|
|
75
|
+
* @example
|
|
76
|
+
* flatten([1, [2, [3, 4]], 5], Infinity)
|
|
77
|
+
* // => [1, 2, 3, 4, 5]
|
|
78
|
+
*/
|
|
79
|
+
export declare const flatten: <T>(arr: any[], depth?: number) => T[];
|
|
80
|
+
/**
|
|
81
|
+
* 在对象数组中按 key-value 查找第一个匹配项
|
|
82
|
+
* @param arr - 对象数组
|
|
83
|
+
* @param key - 要匹配的键名
|
|
84
|
+
* @param value - 要匹配的键值
|
|
85
|
+
* @returns 第一个匹配的对象,无匹配则返回 undefined
|
|
86
|
+
* @example
|
|
87
|
+
* arrFind([{ id: 1, name: "a" }, { id: 2, name: "b" }], "id", 2)
|
|
88
|
+
* // => { id: 2, name: "b" }
|
|
89
|
+
*/
|
|
90
|
+
export declare const arrFind: <T extends Record<string, any>>(arr: T[], key: keyof T, value: T[keyof T]) => T | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* 将对象数组按指定属性分组
|
|
93
|
+
* @param arr - 对象数组
|
|
94
|
+
* @param key - 用于分组的属性名
|
|
95
|
+
* @returns 以属性值为键、对应数组为值的对象
|
|
96
|
+
* @example
|
|
97
|
+
* groupBy([{ type: "fruit", name: "apple" }, { type: "vegetable", name: "carrot" }], "type")
|
|
98
|
+
* // => { fruit: [{ type: "fruit", name: "apple" }], vegetable: [{ type: "vegetable", name: "carrot" }] }
|
|
99
|
+
*/
|
|
100
|
+
export declare const groupBy: <T extends Record<string, any>>(arr: T[], key: keyof T) => Record<string, T[]>;
|
|
101
|
+
/**
|
|
102
|
+
* 移除对象中值为空(null / undefined / 空字符串)的属性
|
|
103
|
+
* @param obj - 待过滤的对象
|
|
104
|
+
* @returns 过滤后的新对象
|
|
105
|
+
* @example
|
|
106
|
+
* filterEmptyValues({ a: 1, b: "", c: null, d: 3 })
|
|
107
|
+
* // => { a: 1, d: 3 }
|
|
108
|
+
*/
|
|
109
|
+
export declare const filterEmptyValues: <T extends Record<string, any>>(obj: T) => Partial<T>;
|
|
110
|
+
/**
|
|
111
|
+
* 根据长度和映射函数快速生成数组
|
|
112
|
+
* @param length - 数组长度
|
|
113
|
+
* @param mapFn - 映射函数,接收索引返回元素值,默认为返回索引
|
|
114
|
+
* @returns 生成的数组
|
|
115
|
+
* @example
|
|
116
|
+
* createRange(5)
|
|
117
|
+
* // => [0, 1, 2, 3, 4]
|
|
118
|
+
*/
|
|
119
|
+
export declare const createRange: <T = number>(length: number, mapFn?: (index: number) => T) => T[];
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -76,3 +76,18 @@ export declare const scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
|
76
76
|
* cleanup(); // 停止监听
|
|
77
77
|
*/
|
|
78
78
|
export declare const onScroll: (callback: (scrollY: number) => void) => () => void;
|
|
79
|
+
/**
|
|
80
|
+
* 监听目标元素是否进入/离开可视区域
|
|
81
|
+
* @param target - 目标 DOM 元素
|
|
82
|
+
* @param onEnter - 元素进入可视区域时的回调
|
|
83
|
+
* @param onLeave - 元素离开可视区域时的回调(可选)
|
|
84
|
+
* @param options - IntersectionObserver 配置项
|
|
85
|
+
* @returns 清理函数,调用后停止观察
|
|
86
|
+
* @example
|
|
87
|
+
* const cleanup = observeIntersection(
|
|
88
|
+
* document.querySelector(".footer")!,
|
|
89
|
+
* () => loadMore(10),
|
|
90
|
+
* );
|
|
91
|
+
* cleanup(); // 停止观察
|
|
92
|
+
*/
|
|
93
|
+
export declare const observeIntersection: (target: Element, onEnter: (entry: IntersectionObserverEntry) => void, onLeave?: (entry: IntersectionObserverEntry) => void, options?: IntersectionObserverInit) => () => void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 防抖 —— 在事件被触发 n 秒后再执行,如果 n 秒内再次触发则重新计时
|
|
3
|
+
* @param fn - 需要防抖的函数
|
|
4
|
+
* @param delay - 延迟时间(毫秒),默认 300
|
|
5
|
+
* @param options.leading - 首次调用是否立即执行,默认 false
|
|
6
|
+
* @param options.trailing - 停止调用后是否延迟执行,默认 true
|
|
7
|
+
* @returns 防抖后的函数,附带 cancel 方法
|
|
8
|
+
* @example
|
|
9
|
+
* const fn = debounce((val: string) => console.log(val), 500)
|
|
10
|
+
* fn("a"); fn("b"); fn("c")
|
|
11
|
+
* // => "c"
|
|
12
|
+
*/
|
|
13
|
+
export declare const debounce: <T extends (...args: any[]) => any>(fn: T, delay?: number, { leading, trailing }?: {
|
|
14
|
+
leading?: boolean;
|
|
15
|
+
trailing?: boolean;
|
|
16
|
+
}) => ((...args: Parameters<T>) => void) & {
|
|
17
|
+
cancel: () => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 节流 —— 固定间隔内最多执行一次,超出频率的调用被忽略
|
|
21
|
+
* @param fn - 需要节流的函数
|
|
22
|
+
* @param interval - 间隔时间(毫秒),默认 300
|
|
23
|
+
* @param options.leading - 首次调用是否立即执行,默认 true
|
|
24
|
+
* @param options.trailing - 最后一次调用后是否尾部执行,默认 true
|
|
25
|
+
* @returns 节流后的函数,附带 cancel 方法
|
|
26
|
+
* @example
|
|
27
|
+
* const fn = throttle((val: string) => console.log(val), 500)
|
|
28
|
+
* fn("a"); fn("b"); fn("c")
|
|
29
|
+
* // => "a",500ms 后输出 "c"
|
|
30
|
+
*/
|
|
31
|
+
export declare const throttle: <T extends (...args: any[]) => any>(fn: T, interval?: number, { leading, trailing }?: {
|
|
32
|
+
leading?: boolean;
|
|
33
|
+
trailing?: boolean;
|
|
34
|
+
}) => ((...args: Parameters<T>) => void) & {
|
|
35
|
+
cancel: () => void;
|
|
36
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -844,6 +844,85 @@ const sortByKey = (arr, key, order = "asc") => {
|
|
|
844
844
|
const toArray = (arrayLike) => {
|
|
845
845
|
return Array.from(arrayLike);
|
|
846
846
|
};
|
|
847
|
+
/**
|
|
848
|
+
* 合并多个数组
|
|
849
|
+
* @param arrays - 一个或多个数组
|
|
850
|
+
* @returns 合并后的新数组
|
|
851
|
+
* @example
|
|
852
|
+
* mergeArrays([1, 2], [3, 4], [5])
|
|
853
|
+
* // => [1, 2, 3, 4, 5]
|
|
854
|
+
*/
|
|
855
|
+
const mergeArrays = (...arrays) => {
|
|
856
|
+
return [].concat(...arrays);
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* 将多维数组扁平化到指定层级
|
|
860
|
+
* @param arr - 多维数组
|
|
861
|
+
* @param depth - 扁平化深度,默认 1(仅展开一层)。传 Infinity 可完全扁平化为一维
|
|
862
|
+
* @returns 扁平化后的数组
|
|
863
|
+
* @example
|
|
864
|
+
* flatten([1, [2, [3, 4]], 5])
|
|
865
|
+
* // => [1, 2, [3, 4], 5]
|
|
866
|
+
* @example
|
|
867
|
+
* flatten([1, [2, [3, 4]], 5], Infinity)
|
|
868
|
+
* // => [1, 2, 3, 4, 5]
|
|
869
|
+
*/
|
|
870
|
+
const flatten = (arr, depth = 1) => {
|
|
871
|
+
return arr.flat(depth);
|
|
872
|
+
};
|
|
873
|
+
/**
|
|
874
|
+
* 在对象数组中按 key-value 查找第一个匹配项
|
|
875
|
+
* @param arr - 对象数组
|
|
876
|
+
* @param key - 要匹配的键名
|
|
877
|
+
* @param value - 要匹配的键值
|
|
878
|
+
* @returns 第一个匹配的对象,无匹配则返回 undefined
|
|
879
|
+
* @example
|
|
880
|
+
* arrFind([{ id: 1, name: "a" }, { id: 2, name: "b" }], "id", 2)
|
|
881
|
+
* // => { id: 2, name: "b" }
|
|
882
|
+
*/
|
|
883
|
+
const arrFind = (arr, key, value) => {
|
|
884
|
+
return arr.find((item) => item[key] === value);
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* 将对象数组按指定属性分组
|
|
888
|
+
* @param arr - 对象数组
|
|
889
|
+
* @param key - 用于分组的属性名
|
|
890
|
+
* @returns 以属性值为键、对应数组为值的对象
|
|
891
|
+
* @example
|
|
892
|
+
* groupBy([{ type: "fruit", name: "apple" }, { type: "vegetable", name: "carrot" }], "type")
|
|
893
|
+
* // => { fruit: [{ type: "fruit", name: "apple" }], vegetable: [{ type: "vegetable", name: "carrot" }] }
|
|
894
|
+
*/
|
|
895
|
+
const groupBy = (arr, key) => {
|
|
896
|
+
return arr.reduce((acc, item) => {
|
|
897
|
+
const groupKey = String(item[key]);
|
|
898
|
+
if (!acc[groupKey]) acc[groupKey] = [];
|
|
899
|
+
acc[groupKey].push(item);
|
|
900
|
+
return acc;
|
|
901
|
+
}, {});
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* 移除对象中值为空(null / undefined / 空字符串)的属性
|
|
905
|
+
* @param obj - 待过滤的对象
|
|
906
|
+
* @returns 过滤后的新对象
|
|
907
|
+
* @example
|
|
908
|
+
* filterEmptyValues({ a: 1, b: "", c: null, d: 3 })
|
|
909
|
+
* // => { a: 1, d: 3 }
|
|
910
|
+
*/
|
|
911
|
+
const filterEmptyValues = (obj) => {
|
|
912
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== void 0 && v !== ""));
|
|
913
|
+
};
|
|
914
|
+
/**
|
|
915
|
+
* 根据长度和映射函数快速生成数组
|
|
916
|
+
* @param length - 数组长度
|
|
917
|
+
* @param mapFn - 映射函数,接收索引返回元素值,默认为返回索引
|
|
918
|
+
* @returns 生成的数组
|
|
919
|
+
* @example
|
|
920
|
+
* createRange(5)
|
|
921
|
+
* // => [0, 1, 2, 3, 4]
|
|
922
|
+
*/
|
|
923
|
+
const createRange = (length, mapFn) => {
|
|
924
|
+
return Array.from({ length }, (_, i) => mapFn ? mapFn(i) : i);
|
|
925
|
+
};
|
|
847
926
|
//#endregion
|
|
848
927
|
//#region src/cookie/index.ts
|
|
849
928
|
/**
|
|
@@ -1019,19 +1098,134 @@ const onScroll = (callback) => {
|
|
|
1019
1098
|
window.addEventListener("scroll", handler);
|
|
1020
1099
|
return () => window.removeEventListener("scroll", handler);
|
|
1021
1100
|
};
|
|
1101
|
+
/**
|
|
1102
|
+
* 监听目标元素是否进入/离开可视区域
|
|
1103
|
+
* @param target - 目标 DOM 元素
|
|
1104
|
+
* @param onEnter - 元素进入可视区域时的回调
|
|
1105
|
+
* @param onLeave - 元素离开可视区域时的回调(可选)
|
|
1106
|
+
* @param options - IntersectionObserver 配置项
|
|
1107
|
+
* @returns 清理函数,调用后停止观察
|
|
1108
|
+
* @example
|
|
1109
|
+
* const cleanup = observeIntersection(
|
|
1110
|
+
* document.querySelector(".footer")!,
|
|
1111
|
+
* () => loadMore(10),
|
|
1112
|
+
* );
|
|
1113
|
+
* cleanup(); // 停止观察
|
|
1114
|
+
*/
|
|
1115
|
+
const observeIntersection = (target, onEnter, onLeave, options) => {
|
|
1116
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1117
|
+
entries.forEach((entry) => {
|
|
1118
|
+
if (entry.isIntersecting) onEnter(entry);
|
|
1119
|
+
else if (onLeave) onLeave(entry);
|
|
1120
|
+
});
|
|
1121
|
+
}, options);
|
|
1122
|
+
observer.observe(target);
|
|
1123
|
+
return () => observer.disconnect();
|
|
1124
|
+
};
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region src/control/index.ts
|
|
1127
|
+
/**
|
|
1128
|
+
* 防抖 —— 在事件被触发 n 秒后再执行,如果 n 秒内再次触发则重新计时
|
|
1129
|
+
* @param fn - 需要防抖的函数
|
|
1130
|
+
* @param delay - 延迟时间(毫秒),默认 300
|
|
1131
|
+
* @param options.leading - 首次调用是否立即执行,默认 false
|
|
1132
|
+
* @param options.trailing - 停止调用后是否延迟执行,默认 true
|
|
1133
|
+
* @returns 防抖后的函数,附带 cancel 方法
|
|
1134
|
+
* @example
|
|
1135
|
+
* const fn = debounce((val: string) => console.log(val), 500)
|
|
1136
|
+
* fn("a"); fn("b"); fn("c")
|
|
1137
|
+
* // => "c"
|
|
1138
|
+
*/
|
|
1139
|
+
const debounce = (fn, delay = 300, { leading = false, trailing = true } = {}) => {
|
|
1140
|
+
let timer = null;
|
|
1141
|
+
let lastArgs = null;
|
|
1142
|
+
let shouldTrail = trailing;
|
|
1143
|
+
const debounced = (...args) => {
|
|
1144
|
+
lastArgs = args;
|
|
1145
|
+
if (timer !== null) {
|
|
1146
|
+
clearTimeout(timer);
|
|
1147
|
+
shouldTrail = trailing;
|
|
1148
|
+
} else if (leading) {
|
|
1149
|
+
fn(...args);
|
|
1150
|
+
shouldTrail = false;
|
|
1151
|
+
}
|
|
1152
|
+
timer = setTimeout(() => {
|
|
1153
|
+
if (shouldTrail && lastArgs) fn(...lastArgs);
|
|
1154
|
+
timer = lastArgs = null;
|
|
1155
|
+
shouldTrail = trailing;
|
|
1156
|
+
}, delay);
|
|
1157
|
+
};
|
|
1158
|
+
debounced.cancel = () => {
|
|
1159
|
+
if (timer) {
|
|
1160
|
+
clearTimeout(timer);
|
|
1161
|
+
timer = lastArgs = null;
|
|
1162
|
+
}
|
|
1163
|
+
shouldTrail = trailing;
|
|
1164
|
+
};
|
|
1165
|
+
return debounced;
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* 节流 —— 固定间隔内最多执行一次,超出频率的调用被忽略
|
|
1169
|
+
* @param fn - 需要节流的函数
|
|
1170
|
+
* @param interval - 间隔时间(毫秒),默认 300
|
|
1171
|
+
* @param options.leading - 首次调用是否立即执行,默认 true
|
|
1172
|
+
* @param options.trailing - 最后一次调用后是否尾部执行,默认 true
|
|
1173
|
+
* @returns 节流后的函数,附带 cancel 方法
|
|
1174
|
+
* @example
|
|
1175
|
+
* const fn = throttle((val: string) => console.log(val), 500)
|
|
1176
|
+
* fn("a"); fn("b"); fn("c")
|
|
1177
|
+
* // => "a",500ms 后输出 "c"
|
|
1178
|
+
*/
|
|
1179
|
+
const throttle = (fn, interval = 300, { leading = true, trailing = true } = {}) => {
|
|
1180
|
+
let timer = null;
|
|
1181
|
+
let lastTime = 0;
|
|
1182
|
+
let pendingArgs = null;
|
|
1183
|
+
const throttled = (...args) => {
|
|
1184
|
+
const now = Date.now();
|
|
1185
|
+
if (!lastTime && !leading) lastTime = now;
|
|
1186
|
+
const remaining = interval - (now - lastTime);
|
|
1187
|
+
pendingArgs = args;
|
|
1188
|
+
if (remaining <= 0) {
|
|
1189
|
+
if (timer) {
|
|
1190
|
+
clearTimeout(timer);
|
|
1191
|
+
timer = null;
|
|
1192
|
+
}
|
|
1193
|
+
fn(...args);
|
|
1194
|
+
lastTime = now;
|
|
1195
|
+
pendingArgs = null;
|
|
1196
|
+
} else if (trailing && !timer) timer = setTimeout(() => {
|
|
1197
|
+
timer = null;
|
|
1198
|
+
lastTime = leading ? Date.now() : 0;
|
|
1199
|
+
if (pendingArgs) fn(...pendingArgs);
|
|
1200
|
+
pendingArgs = null;
|
|
1201
|
+
}, remaining);
|
|
1202
|
+
};
|
|
1203
|
+
throttled.cancel = () => {
|
|
1204
|
+
if (timer) {
|
|
1205
|
+
clearTimeout(timer);
|
|
1206
|
+
timer = pendingArgs = null;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
return throttled;
|
|
1210
|
+
};
|
|
1022
1211
|
//#endregion
|
|
1212
|
+
exports.arrFind = arrFind;
|
|
1023
1213
|
exports.camelToKebab = camelToKebab;
|
|
1024
1214
|
exports.capitalize = capitalize;
|
|
1025
1215
|
exports.compactCN = compactCN;
|
|
1026
1216
|
exports.compactEN = compactEN;
|
|
1027
1217
|
exports.copyToClipboard = copyToClipboard;
|
|
1218
|
+
exports.createRange = createRange;
|
|
1219
|
+
exports.debounce = debounce;
|
|
1028
1220
|
exports.deepClone = deepClone;
|
|
1029
1221
|
exports.deepCloneWithJSON = deepCloneWithJSON;
|
|
1030
1222
|
exports.delCookie = delCookie;
|
|
1031
1223
|
exports.downloadFile = downloadFile;
|
|
1032
1224
|
exports.exportJSON = exportJSON;
|
|
1225
|
+
exports.filterEmptyValues = filterEmptyValues;
|
|
1033
1226
|
exports.firstLower = firstLower;
|
|
1034
1227
|
exports.firstUpper = firstUpper;
|
|
1228
|
+
exports.flatten = flatten;
|
|
1035
1229
|
exports.formatFull = formatFull;
|
|
1036
1230
|
exports.formatFullReplace = formatFullReplace;
|
|
1037
1231
|
exports.formatNum = formatNum;
|
|
@@ -1041,6 +1235,7 @@ exports.formatYMD = formatYMD;
|
|
|
1041
1235
|
exports.getCookie = getCookie;
|
|
1042
1236
|
exports.getUrlParam = getUrlParam;
|
|
1043
1237
|
exports.getUrlParams = getUrlParams;
|
|
1238
|
+
exports.groupBy = groupBy;
|
|
1044
1239
|
exports.is = is;
|
|
1045
1240
|
exports.isArray = isArray;
|
|
1046
1241
|
exports.isAsyncFunction = isAsyncFunction;
|
|
@@ -1074,6 +1269,8 @@ exports.localGet = localGet;
|
|
|
1074
1269
|
exports.localRm = localRm;
|
|
1075
1270
|
exports.localSet = localSet;
|
|
1076
1271
|
exports.maskPhone = maskPhone;
|
|
1272
|
+
exports.mergeArrays = mergeArrays;
|
|
1273
|
+
exports.observeIntersection = observeIntersection;
|
|
1077
1274
|
exports.onScroll = onScroll;
|
|
1078
1275
|
exports.percentCN = percentCN;
|
|
1079
1276
|
exports.reverse = reverse;
|
|
@@ -1086,6 +1283,7 @@ exports.sortByKey = sortByKey;
|
|
|
1086
1283
|
exports.sortNumAsc = sortNumAsc;
|
|
1087
1284
|
exports.sortNumDesc = sortNumDesc;
|
|
1088
1285
|
exports.spacePhone = spacePhone;
|
|
1286
|
+
exports.throttle = throttle;
|
|
1089
1287
|
exports.toArray = toArray;
|
|
1090
1288
|
exports.toCamel = toCamel;
|
|
1091
1289
|
exports.toQueryString = toQueryString;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -843,6 +843,85 @@ const sortByKey = (arr, key, order = "asc") => {
|
|
|
843
843
|
const toArray = (arrayLike) => {
|
|
844
844
|
return Array.from(arrayLike);
|
|
845
845
|
};
|
|
846
|
+
/**
|
|
847
|
+
* 合并多个数组
|
|
848
|
+
* @param arrays - 一个或多个数组
|
|
849
|
+
* @returns 合并后的新数组
|
|
850
|
+
* @example
|
|
851
|
+
* mergeArrays([1, 2], [3, 4], [5])
|
|
852
|
+
* // => [1, 2, 3, 4, 5]
|
|
853
|
+
*/
|
|
854
|
+
const mergeArrays = (...arrays) => {
|
|
855
|
+
return [].concat(...arrays);
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* 将多维数组扁平化到指定层级
|
|
859
|
+
* @param arr - 多维数组
|
|
860
|
+
* @param depth - 扁平化深度,默认 1(仅展开一层)。传 Infinity 可完全扁平化为一维
|
|
861
|
+
* @returns 扁平化后的数组
|
|
862
|
+
* @example
|
|
863
|
+
* flatten([1, [2, [3, 4]], 5])
|
|
864
|
+
* // => [1, 2, [3, 4], 5]
|
|
865
|
+
* @example
|
|
866
|
+
* flatten([1, [2, [3, 4]], 5], Infinity)
|
|
867
|
+
* // => [1, 2, 3, 4, 5]
|
|
868
|
+
*/
|
|
869
|
+
const flatten = (arr, depth = 1) => {
|
|
870
|
+
return arr.flat(depth);
|
|
871
|
+
};
|
|
872
|
+
/**
|
|
873
|
+
* 在对象数组中按 key-value 查找第一个匹配项
|
|
874
|
+
* @param arr - 对象数组
|
|
875
|
+
* @param key - 要匹配的键名
|
|
876
|
+
* @param value - 要匹配的键值
|
|
877
|
+
* @returns 第一个匹配的对象,无匹配则返回 undefined
|
|
878
|
+
* @example
|
|
879
|
+
* arrFind([{ id: 1, name: "a" }, { id: 2, name: "b" }], "id", 2)
|
|
880
|
+
* // => { id: 2, name: "b" }
|
|
881
|
+
*/
|
|
882
|
+
const arrFind = (arr, key, value) => {
|
|
883
|
+
return arr.find((item) => item[key] === value);
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* 将对象数组按指定属性分组
|
|
887
|
+
* @param arr - 对象数组
|
|
888
|
+
* @param key - 用于分组的属性名
|
|
889
|
+
* @returns 以属性值为键、对应数组为值的对象
|
|
890
|
+
* @example
|
|
891
|
+
* groupBy([{ type: "fruit", name: "apple" }, { type: "vegetable", name: "carrot" }], "type")
|
|
892
|
+
* // => { fruit: [{ type: "fruit", name: "apple" }], vegetable: [{ type: "vegetable", name: "carrot" }] }
|
|
893
|
+
*/
|
|
894
|
+
const groupBy = (arr, key) => {
|
|
895
|
+
return arr.reduce((acc, item) => {
|
|
896
|
+
const groupKey = String(item[key]);
|
|
897
|
+
if (!acc[groupKey]) acc[groupKey] = [];
|
|
898
|
+
acc[groupKey].push(item);
|
|
899
|
+
return acc;
|
|
900
|
+
}, {});
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* 移除对象中值为空(null / undefined / 空字符串)的属性
|
|
904
|
+
* @param obj - 待过滤的对象
|
|
905
|
+
* @returns 过滤后的新对象
|
|
906
|
+
* @example
|
|
907
|
+
* filterEmptyValues({ a: 1, b: "", c: null, d: 3 })
|
|
908
|
+
* // => { a: 1, d: 3 }
|
|
909
|
+
*/
|
|
910
|
+
const filterEmptyValues = (obj) => {
|
|
911
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== void 0 && v !== ""));
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* 根据长度和映射函数快速生成数组
|
|
915
|
+
* @param length - 数组长度
|
|
916
|
+
* @param mapFn - 映射函数,接收索引返回元素值,默认为返回索引
|
|
917
|
+
* @returns 生成的数组
|
|
918
|
+
* @example
|
|
919
|
+
* createRange(5)
|
|
920
|
+
* // => [0, 1, 2, 3, 4]
|
|
921
|
+
*/
|
|
922
|
+
const createRange = (length, mapFn) => {
|
|
923
|
+
return Array.from({ length }, (_, i) => mapFn ? mapFn(i) : i);
|
|
924
|
+
};
|
|
846
925
|
//#endregion
|
|
847
926
|
//#region src/cookie/index.ts
|
|
848
927
|
/**
|
|
@@ -1018,5 +1097,115 @@ const onScroll = (callback) => {
|
|
|
1018
1097
|
window.addEventListener("scroll", handler);
|
|
1019
1098
|
return () => window.removeEventListener("scroll", handler);
|
|
1020
1099
|
};
|
|
1100
|
+
/**
|
|
1101
|
+
* 监听目标元素是否进入/离开可视区域
|
|
1102
|
+
* @param target - 目标 DOM 元素
|
|
1103
|
+
* @param onEnter - 元素进入可视区域时的回调
|
|
1104
|
+
* @param onLeave - 元素离开可视区域时的回调(可选)
|
|
1105
|
+
* @param options - IntersectionObserver 配置项
|
|
1106
|
+
* @returns 清理函数,调用后停止观察
|
|
1107
|
+
* @example
|
|
1108
|
+
* const cleanup = observeIntersection(
|
|
1109
|
+
* document.querySelector(".footer")!,
|
|
1110
|
+
* () => loadMore(10),
|
|
1111
|
+
* );
|
|
1112
|
+
* cleanup(); // 停止观察
|
|
1113
|
+
*/
|
|
1114
|
+
const observeIntersection = (target, onEnter, onLeave, options) => {
|
|
1115
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1116
|
+
entries.forEach((entry) => {
|
|
1117
|
+
if (entry.isIntersecting) onEnter(entry);
|
|
1118
|
+
else if (onLeave) onLeave(entry);
|
|
1119
|
+
});
|
|
1120
|
+
}, options);
|
|
1121
|
+
observer.observe(target);
|
|
1122
|
+
return () => observer.disconnect();
|
|
1123
|
+
};
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region src/control/index.ts
|
|
1126
|
+
/**
|
|
1127
|
+
* 防抖 —— 在事件被触发 n 秒后再执行,如果 n 秒内再次触发则重新计时
|
|
1128
|
+
* @param fn - 需要防抖的函数
|
|
1129
|
+
* @param delay - 延迟时间(毫秒),默认 300
|
|
1130
|
+
* @param options.leading - 首次调用是否立即执行,默认 false
|
|
1131
|
+
* @param options.trailing - 停止调用后是否延迟执行,默认 true
|
|
1132
|
+
* @returns 防抖后的函数,附带 cancel 方法
|
|
1133
|
+
* @example
|
|
1134
|
+
* const fn = debounce((val: string) => console.log(val), 500)
|
|
1135
|
+
* fn("a"); fn("b"); fn("c")
|
|
1136
|
+
* // => "c"
|
|
1137
|
+
*/
|
|
1138
|
+
const debounce = (fn, delay = 300, { leading = false, trailing = true } = {}) => {
|
|
1139
|
+
let timer = null;
|
|
1140
|
+
let lastArgs = null;
|
|
1141
|
+
let shouldTrail = trailing;
|
|
1142
|
+
const debounced = (...args) => {
|
|
1143
|
+
lastArgs = args;
|
|
1144
|
+
if (timer !== null) {
|
|
1145
|
+
clearTimeout(timer);
|
|
1146
|
+
shouldTrail = trailing;
|
|
1147
|
+
} else if (leading) {
|
|
1148
|
+
fn(...args);
|
|
1149
|
+
shouldTrail = false;
|
|
1150
|
+
}
|
|
1151
|
+
timer = setTimeout(() => {
|
|
1152
|
+
if (shouldTrail && lastArgs) fn(...lastArgs);
|
|
1153
|
+
timer = lastArgs = null;
|
|
1154
|
+
shouldTrail = trailing;
|
|
1155
|
+
}, delay);
|
|
1156
|
+
};
|
|
1157
|
+
debounced.cancel = () => {
|
|
1158
|
+
if (timer) {
|
|
1159
|
+
clearTimeout(timer);
|
|
1160
|
+
timer = lastArgs = null;
|
|
1161
|
+
}
|
|
1162
|
+
shouldTrail = trailing;
|
|
1163
|
+
};
|
|
1164
|
+
return debounced;
|
|
1165
|
+
};
|
|
1166
|
+
/**
|
|
1167
|
+
* 节流 —— 固定间隔内最多执行一次,超出频率的调用被忽略
|
|
1168
|
+
* @param fn - 需要节流的函数
|
|
1169
|
+
* @param interval - 间隔时间(毫秒),默认 300
|
|
1170
|
+
* @param options.leading - 首次调用是否立即执行,默认 true
|
|
1171
|
+
* @param options.trailing - 最后一次调用后是否尾部执行,默认 true
|
|
1172
|
+
* @returns 节流后的函数,附带 cancel 方法
|
|
1173
|
+
* @example
|
|
1174
|
+
* const fn = throttle((val: string) => console.log(val), 500)
|
|
1175
|
+
* fn("a"); fn("b"); fn("c")
|
|
1176
|
+
* // => "a",500ms 后输出 "c"
|
|
1177
|
+
*/
|
|
1178
|
+
const throttle = (fn, interval = 300, { leading = true, trailing = true } = {}) => {
|
|
1179
|
+
let timer = null;
|
|
1180
|
+
let lastTime = 0;
|
|
1181
|
+
let pendingArgs = null;
|
|
1182
|
+
const throttled = (...args) => {
|
|
1183
|
+
const now = Date.now();
|
|
1184
|
+
if (!lastTime && !leading) lastTime = now;
|
|
1185
|
+
const remaining = interval - (now - lastTime);
|
|
1186
|
+
pendingArgs = args;
|
|
1187
|
+
if (remaining <= 0) {
|
|
1188
|
+
if (timer) {
|
|
1189
|
+
clearTimeout(timer);
|
|
1190
|
+
timer = null;
|
|
1191
|
+
}
|
|
1192
|
+
fn(...args);
|
|
1193
|
+
lastTime = now;
|
|
1194
|
+
pendingArgs = null;
|
|
1195
|
+
} else if (trailing && !timer) timer = setTimeout(() => {
|
|
1196
|
+
timer = null;
|
|
1197
|
+
lastTime = leading ? Date.now() : 0;
|
|
1198
|
+
if (pendingArgs) fn(...pendingArgs);
|
|
1199
|
+
pendingArgs = null;
|
|
1200
|
+
}, remaining);
|
|
1201
|
+
};
|
|
1202
|
+
throttled.cancel = () => {
|
|
1203
|
+
if (timer) {
|
|
1204
|
+
clearTimeout(timer);
|
|
1205
|
+
timer = pendingArgs = null;
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
return throttled;
|
|
1209
|
+
};
|
|
1021
1210
|
//#endregion
|
|
1022
|
-
export { camelToKebab, capitalize, compactCN, compactEN, copyToClipboard, deepClone, deepCloneWithJSON, delCookie, downloadFile, exportJSON, firstLower, firstUpper, formatFull, formatFullReplace, formatNum, formatRmb, formatWeek, formatYMD, getCookie, getUrlParam, getUrlParams, is, isArray, isAsyncFunction, isBoolean, isDate, isDef, isElement, isEmpty, isEmptySv, isEqual, isFloat, isFunction, isHexColor, isIOS, isInt, isNull, isNullOrUnDef, isNumber, isObject, isPC, isPrimitive, isPromise, isString, isSymbol, isUnDef, isValidEmail, isWindow, kebabToCamel, localClear, localGet, localRm, localSet, maskPhone, onScroll, percentCN, reverse, scrollToBottom, scrollToTop, setCookie, shallowClone, signed, sortByKey, sortNumAsc, sortNumDesc, spacePhone, toArray, toCamel, toQueryString, trimAll, truncate, truncateByWords, unique, uniqueByKey };
|
|
1211
|
+
export { arrFind, camelToKebab, capitalize, compactCN, compactEN, copyToClipboard, createRange, debounce, deepClone, deepCloneWithJSON, delCookie, downloadFile, exportJSON, filterEmptyValues, firstLower, firstUpper, flatten, formatFull, formatFullReplace, formatNum, formatRmb, formatWeek, formatYMD, getCookie, getUrlParam, getUrlParams, groupBy, is, isArray, isAsyncFunction, isBoolean, isDate, isDef, isElement, isEmpty, isEmptySv, isEqual, isFloat, isFunction, isHexColor, isIOS, isInt, isNull, isNullOrUnDef, isNumber, isObject, isPC, isPrimitive, isPromise, isString, isSymbol, isUnDef, isValidEmail, isWindow, kebabToCamel, localClear, localGet, localRm, localSet, maskPhone, mergeArrays, observeIntersection, onScroll, percentCN, reverse, scrollToBottom, scrollToTop, setCookie, shallowClone, signed, sortByKey, sortNumAsc, sortNumDesc, spacePhone, throttle, toArray, toCamel, toQueryString, trimAll, truncate, truncateByWords, unique, uniqueByKey };
|
package/package.json
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pen-it",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "用笔锚定记忆中的前端常用工具函数,一个轻量级的前端实用程序库,支持类型检查、日期/数字格式化、字符串操作、数据存储、深度克隆、数组操作、Cookie 以及浏览器 API",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.cjs",
|
|
7
|
-
"module": "dist/index.js",
|
|
8
|
-
"types": "dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"require": "./dist/index.cjs",
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "rimraf dist && rolldown -c rolldown.config.js",
|
|
18
|
-
"test": "vitest"
|
|
19
|
-
},
|
|
20
|
-
"files": [
|
|
21
|
-
"dist"
|
|
22
|
-
],
|
|
23
|
-
"keywords": [
|
|
24
|
-
"utils",
|
|
25
|
-
"utilities",
|
|
26
|
-
"toolkit",
|
|
27
|
-
"helpers",
|
|
28
|
-
"frontend",
|
|
29
|
-
"typescript",
|
|
30
|
-
"type-checking",
|
|
31
|
-
"date-format",
|
|
32
|
-
"number-format",
|
|
33
|
-
"deep-clone",
|
|
34
|
-
"string-manipulation",
|
|
35
|
-
"array-operations",
|
|
36
|
-
"cookie",
|
|
37
|
-
"localstorage",
|
|
38
|
-
"browser-api",
|
|
39
|
-
"debounce",
|
|
40
|
-
"throttle"
|
|
41
|
-
],
|
|
42
|
-
"author": "newborn_calf",
|
|
43
|
-
"license": "MIT",
|
|
44
|
-
"repository": {
|
|
45
|
-
"type": "git",
|
|
46
|
-
"url": "https://github.com/Libao-Jun/pen-it"
|
|
47
|
-
},
|
|
48
|
-
"homepage": "https://github.com/Libao-Jun/pen-it#readme",
|
|
49
|
-
"bugs": {
|
|
50
|
-
"url": "https://github.com/Libao-Jun/pen-it/issues"
|
|
51
|
-
},
|
|
52
|
-
"packageManager": "pnpm@10.19.0",
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@rollup/plugin-typescript": "^12.3.0",
|
|
55
|
-
"@vitest/coverage-v8": "^4.1.8",
|
|
56
|
-
"jsdom": "^29.1.1",
|
|
57
|
-
"rimraf": "^6.1.3",
|
|
58
|
-
"rolldown": "^1.0.3",
|
|
59
|
-
"tslib": "^2.8.1",
|
|
60
|
-
"typescript": "^6.0.3",
|
|
61
|
-
"vitest": "^4.1.8"
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pen-it",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "用笔锚定记忆中的前端常用工具函数,一个轻量级的前端实用程序库,支持类型检查、日期/数字格式化、字符串操作、数据存储、深度克隆、数组操作、Cookie 以及浏览器 API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "rimraf dist && rolldown -c rolldown.config.js",
|
|
18
|
+
"test": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"utils",
|
|
25
|
+
"utilities",
|
|
26
|
+
"toolkit",
|
|
27
|
+
"helpers",
|
|
28
|
+
"frontend",
|
|
29
|
+
"typescript",
|
|
30
|
+
"type-checking",
|
|
31
|
+
"date-format",
|
|
32
|
+
"number-format",
|
|
33
|
+
"deep-clone",
|
|
34
|
+
"string-manipulation",
|
|
35
|
+
"array-operations",
|
|
36
|
+
"cookie",
|
|
37
|
+
"localstorage",
|
|
38
|
+
"browser-api",
|
|
39
|
+
"debounce",
|
|
40
|
+
"throttle"
|
|
41
|
+
],
|
|
42
|
+
"author": "newborn_calf",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/Libao-Jun/pen-it"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/Libao-Jun/pen-it#readme",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/Libao-Jun/pen-it/issues"
|
|
51
|
+
},
|
|
52
|
+
"packageManager": "pnpm@10.19.0",
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
55
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
56
|
+
"jsdom": "^29.1.1",
|
|
57
|
+
"rimraf": "^6.1.3",
|
|
58
|
+
"rolldown": "^1.0.3",
|
|
59
|
+
"tslib": "^2.8.1",
|
|
60
|
+
"typescript": "^6.0.3",
|
|
61
|
+
"vitest": "^4.1.8"
|
|
62
|
+
}
|
|
63
63
|
}
|