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 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
  ```
@@ -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[];
@@ -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
@@ -5,3 +5,4 @@ export * from './copy';
5
5
  export * from './array';
6
6
  export * from './cookie';
7
7
  export * from './browser';
8
+ export * from './control';
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",
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
  }