sculp-js 1.7.1 → 1.7.2

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.
Files changed (59) hide show
  1. package/lib/cjs/array.js +1 -1
  2. package/lib/cjs/async.js +1 -1
  3. package/lib/cjs/base64.js +1 -1
  4. package/lib/cjs/clipboard.js +1 -1
  5. package/lib/cjs/cookie.js +1 -1
  6. package/lib/cjs/date.js +1 -1
  7. package/lib/cjs/dom.js +1 -1
  8. package/lib/cjs/download.js +1 -1
  9. package/lib/cjs/easing.js +1 -1
  10. package/lib/cjs/file.js +1 -1
  11. package/lib/cjs/func.js +1 -1
  12. package/lib/cjs/index.js +1 -1
  13. package/lib/cjs/math.js +1 -1
  14. package/lib/cjs/number.js +1 -1
  15. package/lib/cjs/object.js +61 -24
  16. package/lib/cjs/path.js +1 -1
  17. package/lib/cjs/qs.js +1 -1
  18. package/lib/cjs/random.js +1 -1
  19. package/lib/cjs/string.js +1 -1
  20. package/lib/cjs/tooltip.js +1 -1
  21. package/lib/cjs/tree.js +30 -10
  22. package/lib/cjs/type.js +1 -1
  23. package/lib/cjs/unique.js +1 -1
  24. package/lib/cjs/url.js +1 -1
  25. package/lib/cjs/validator.js +1 -1
  26. package/lib/cjs/variable.js +1 -1
  27. package/lib/cjs/watermark.js +1 -1
  28. package/lib/cjs/we-decode.js +1 -1
  29. package/lib/es/array.js +1 -1
  30. package/lib/es/async.js +1 -1
  31. package/lib/es/base64.js +1 -1
  32. package/lib/es/clipboard.js +1 -1
  33. package/lib/es/cookie.js +1 -1
  34. package/lib/es/date.js +1 -1
  35. package/lib/es/dom.js +1 -1
  36. package/lib/es/download.js +1 -1
  37. package/lib/es/easing.js +1 -1
  38. package/lib/es/file.js +1 -1
  39. package/lib/es/func.js +1 -1
  40. package/lib/es/index.js +1 -1
  41. package/lib/es/math.js +1 -1
  42. package/lib/es/number.js +1 -1
  43. package/lib/es/object.js +61 -24
  44. package/lib/es/path.js +1 -1
  45. package/lib/es/qs.js +1 -1
  46. package/lib/es/random.js +1 -1
  47. package/lib/es/string.js +1 -1
  48. package/lib/es/tooltip.js +1 -1
  49. package/lib/es/tree.js +30 -10
  50. package/lib/es/type.js +1 -1
  51. package/lib/es/unique.js +1 -1
  52. package/lib/es/url.js +1 -1
  53. package/lib/es/validator.js +1 -1
  54. package/lib/es/variable.js +1 -1
  55. package/lib/es/watermark.js +1 -1
  56. package/lib/es/we-decode.js +1 -1
  57. package/lib/index.d.ts +29 -9
  58. package/lib/umd/index.js +89 -33
  59. package/package.json +1 -1
package/lib/cjs/array.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/async.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/base64.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/cookie.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/date.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/dom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/easing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/file.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/func.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/math.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/number.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/object.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -83,7 +83,7 @@ function objectPick(obj, keys) {
83
83
  return obj2;
84
84
  }
85
85
  /**
86
- * 对象祛除
86
+ * 对象去除
87
87
  * @param {O} obj
88
88
  * @param {K} keys
89
89
  * @returns {Pick<O, ArrayElements<K>>}
@@ -198,12 +198,16 @@ function objectGet(obj, path, strict = false) {
198
198
  }
199
199
  /**
200
200
  * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
201
+ *
202
+ * 包含对null、原始值、对象循环引用的处理
203
+ *
204
+ * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
201
205
  * @param {T} source
202
206
  * @param {WeakMap} map
203
207
  * @returns {T}
204
208
  */
205
209
  function cloneDeep(source, map = new WeakMap()) {
206
- // 处理原始类型(非对象/数组)和 null/undefined
210
+ // 处理原始类型和 null/undefined
207
211
  if (source === null || typeof source !== 'object') {
208
212
  return source;
209
213
  }
@@ -211,28 +215,33 @@ function cloneDeep(source, map = new WeakMap()) {
211
215
  if (map.has(source)) {
212
216
  return map.get(source);
213
217
  }
214
- // 处理 Date 类型
218
+ // 处理 ArrayBuffer
219
+ if (source instanceof ArrayBuffer) {
220
+ const copy = new ArrayBuffer(source.byteLength);
221
+ new Uint8Array(copy).set(new Uint8Array(source));
222
+ map.set(source, copy);
223
+ return copy;
224
+ }
225
+ // 处理 DataView 和 TypedArray (Uint8Array 等)
226
+ if (ArrayBuffer.isView(source)) {
227
+ const constructor = source.constructor;
228
+ const bufferCopy = cloneDeep(source.buffer, map);
229
+ return new constructor(bufferCopy, source.byteOffset, source.length);
230
+ }
231
+ // 处理 Date 对象
215
232
  if (source instanceof Date) {
216
233
  const copy = new Date(source.getTime());
217
234
  map.set(source, copy);
218
235
  return copy;
219
236
  }
220
- // 处理 RegExp 类型
237
+ // 处理 RegExp 对象
221
238
  if (source instanceof RegExp) {
222
239
  const copy = new RegExp(source.source, source.flags);
240
+ copy.lastIndex = source.lastIndex; // 保留匹配状态
223
241
  map.set(source, copy);
224
242
  return copy;
225
243
  }
226
- // 处理数组类型
227
- if (Array.isArray(source)) {
228
- const copy = [];
229
- map.set(source, copy);
230
- for (const item of source) {
231
- copy.push(cloneDeep(item, map));
232
- }
233
- return copy;
234
- }
235
- // 处理 Map 类型
244
+ // 处理 Map
236
245
  if (source instanceof Map) {
237
246
  const copy = new Map();
238
247
  map.set(source, copy);
@@ -241,7 +250,7 @@ function cloneDeep(source, map = new WeakMap()) {
241
250
  });
242
251
  return copy;
243
252
  }
244
- // 处理 Set 类型
253
+ // 处理 Set
245
254
  if (source instanceof Set) {
246
255
  const copy = new Set();
247
256
  map.set(source, copy);
@@ -250,19 +259,47 @@ function cloneDeep(source, map = new WeakMap()) {
250
259
  });
251
260
  return copy;
252
261
  }
253
- // 处理 ArrayBuffer 类型
254
- if (source instanceof ArrayBuffer) {
255
- const copy = new ArrayBuffer(source.byteLength);
256
- new Uint8Array(copy).set(new Uint8Array(source));
262
+ // 处理数组 (包含稀疏数组)
263
+ if (Array.isArray(source)) {
264
+ const copy = Array.from({ length: source.length });
257
265
  map.set(source, copy);
266
+ // 克隆所有有效索引
267
+ for (let i = 0; i < source.length; i++) {
268
+ if (i in source) {
269
+ // 保留空位
270
+ copy[i] = cloneDeep(source[i], map);
271
+ }
272
+ }
273
+ // 克隆数组的自定义属性
274
+ const descriptors = Object.getOwnPropertyDescriptors(source);
275
+ for (const key of Reflect.ownKeys(descriptors)) {
276
+ Object.defineProperty(copy, key, {
277
+ ...descriptors[key],
278
+ value: cloneDeep(descriptors[key].value, map)
279
+ });
280
+ }
258
281
  return copy;
259
282
  }
260
- // 处理普通对象(包括原型链继承)
283
+ // 处理普通对象和类实例
261
284
  const copy = Object.create(Object.getPrototypeOf(source));
262
285
  map.set(source, copy);
263
- for (const key of Reflect.ownKeys(source)) {
264
- const value = source[key];
265
- copy[key] = cloneDeep(value, map);
286
+ const descriptors = Object.getOwnPropertyDescriptors(source);
287
+ for (const key of Reflect.ownKeys(descriptors)) {
288
+ const descriptor = descriptors[key];
289
+ if ('value' in descriptor) {
290
+ // 克隆数据属性
291
+ descriptor.value = cloneDeep(descriptor.value, map);
292
+ }
293
+ else {
294
+ // 处理访问器属性 (getter/setter)
295
+ if (descriptor.get) {
296
+ descriptor.get = cloneDeep(descriptor.get, map);
297
+ }
298
+ if (descriptor.set) {
299
+ descriptor.set = cloneDeep(descriptor.set, map);
300
+ }
301
+ }
302
+ Object.defineProperty(copy, key, descriptor);
266
303
  }
267
304
  return copy;
268
305
  }
package/lib/cjs/path.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/qs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/random.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/string.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/tree.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -7,12 +7,14 @@
7
7
  'use strict';
8
8
 
9
9
  var object = require('./object.js');
10
+ var type = require('./type.js');
10
11
 
11
12
  const defaultFieldOptions = { keyField: 'key', childField: 'children', pidField: 'pid' };
12
13
  const defaultSearchTreeOptions = {
13
14
  childField: 'children',
14
15
  nameField: 'name',
15
- ignoreEmptyChild: false
16
+ removeEmptyChild: false,
17
+ ignoreCase: true
16
18
  };
17
19
  /**
18
20
  * 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
@@ -69,7 +71,9 @@ function forEachDeep(tree, iterator, children = 'children', isReverse = false) {
69
71
  walk(tree, null);
70
72
  }
71
73
  /**
72
- * 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
74
+ * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
75
+ *
76
+ * 可遍历任何带有 length 属性和数字键的类数组对象
73
77
  * @param {ArrayLike<V>} tree 树形数据
74
78
  * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
75
79
  * @param {string} children 定制子元素的key
@@ -305,20 +309,36 @@ function flatTree(treeList, options = defaultFieldOptions) {
305
309
  }
306
310
  /**
307
311
  * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
308
- * @param {any[]} nodes
309
- * @param {string} query
312
+ * 以下搜索条件二选一,按先后优先级处理:
313
+ * 1. 过滤函数filter, 返回true/false
314
+ * 2. 匹配关键词,支持是否启用忽略大小写来判断
315
+ *
316
+ * 有以下特性:
317
+ * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
318
+ * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
319
+ * @param {V[]} nodes
320
+ * @param {IFilterCondition} filterCondition
310
321
  * @param {ISearchTreeOpts} options
311
- * @returns {any[]}
322
+ * @returns {V[]}
312
323
  */
313
- function fuzzySearchTree(nodes, query, options = defaultSearchTreeOptions) {
324
+ function fuzzySearchTree(nodes, filterCondition, options = defaultSearchTreeOptions) {
325
+ if (!type.objectHas(filterCondition, 'filter') &&
326
+ (!type.objectHas(filterCondition, 'keyword') || type.isEmpty(filterCondition.keyword))) {
327
+ return nodes;
328
+ }
314
329
  const result = [];
315
330
  for (const node of nodes) {
316
331
  // 递归检查子节点是否匹配
317
332
  const matchedChildren = node[options.childField] && node[options.childField].length > 0
318
- ? fuzzySearchTree(node[options.childField] || [], query, options)
333
+ ? fuzzySearchTree(node[options.childField] || [], filterCondition, options)
319
334
  : [];
320
335
  // 检查当前节点是否匹配或者有匹配的子节点
321
- if (node[options.nameField].toLowerCase().includes(query.toLowerCase()) || matchedChildren.length > 0) {
336
+ if ((type.objectHas(filterCondition, 'filter')
337
+ ? filterCondition.filter(node)
338
+ : !options.ignoreCase
339
+ ? node[options.nameField].includes(filterCondition.keyword)
340
+ : node[options.nameField].toLowerCase().includes(filterCondition.keyword.toLowerCase())) ||
341
+ matchedChildren.length > 0) {
322
342
  // 将当前节点加入结果中
323
343
  if (node[options.childField]) {
324
344
  if (matchedChildren.length > 0) {
@@ -327,7 +347,7 @@ function fuzzySearchTree(nodes, query, options = defaultSearchTreeOptions) {
327
347
  [options.childField]: matchedChildren // 包含匹配的子节点
328
348
  });
329
349
  }
330
- else if (options.ignoreEmptyChild) {
350
+ else if (options.removeEmptyChild) {
331
351
  node[options.childField] && delete node[options.childField];
332
352
  result.push({
333
353
  ...node
package/lib/cjs/type.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/unique.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/cjs/url.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/array.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/async.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/base64.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/cookie.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/date.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/dom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/easing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/file.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/func.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/math.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/number.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/object.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -81,7 +81,7 @@ function objectPick(obj, keys) {
81
81
  return obj2;
82
82
  }
83
83
  /**
84
- * 对象祛除
84
+ * 对象去除
85
85
  * @param {O} obj
86
86
  * @param {K} keys
87
87
  * @returns {Pick<O, ArrayElements<K>>}
@@ -196,12 +196,16 @@ function objectGet(obj, path, strict = false) {
196
196
  }
197
197
  /**
198
198
  * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
199
+ *
200
+ * 包含对null、原始值、对象循环引用的处理
201
+ *
202
+ * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
199
203
  * @param {T} source
200
204
  * @param {WeakMap} map
201
205
  * @returns {T}
202
206
  */
203
207
  function cloneDeep(source, map = new WeakMap()) {
204
- // 处理原始类型(非对象/数组)和 null/undefined
208
+ // 处理原始类型和 null/undefined
205
209
  if (source === null || typeof source !== 'object') {
206
210
  return source;
207
211
  }
@@ -209,28 +213,33 @@ function cloneDeep(source, map = new WeakMap()) {
209
213
  if (map.has(source)) {
210
214
  return map.get(source);
211
215
  }
212
- // 处理 Date 类型
216
+ // 处理 ArrayBuffer
217
+ if (source instanceof ArrayBuffer) {
218
+ const copy = new ArrayBuffer(source.byteLength);
219
+ new Uint8Array(copy).set(new Uint8Array(source));
220
+ map.set(source, copy);
221
+ return copy;
222
+ }
223
+ // 处理 DataView 和 TypedArray (Uint8Array 等)
224
+ if (ArrayBuffer.isView(source)) {
225
+ const constructor = source.constructor;
226
+ const bufferCopy = cloneDeep(source.buffer, map);
227
+ return new constructor(bufferCopy, source.byteOffset, source.length);
228
+ }
229
+ // 处理 Date 对象
213
230
  if (source instanceof Date) {
214
231
  const copy = new Date(source.getTime());
215
232
  map.set(source, copy);
216
233
  return copy;
217
234
  }
218
- // 处理 RegExp 类型
235
+ // 处理 RegExp 对象
219
236
  if (source instanceof RegExp) {
220
237
  const copy = new RegExp(source.source, source.flags);
238
+ copy.lastIndex = source.lastIndex; // 保留匹配状态
221
239
  map.set(source, copy);
222
240
  return copy;
223
241
  }
224
- // 处理数组类型
225
- if (Array.isArray(source)) {
226
- const copy = [];
227
- map.set(source, copy);
228
- for (const item of source) {
229
- copy.push(cloneDeep(item, map));
230
- }
231
- return copy;
232
- }
233
- // 处理 Map 类型
242
+ // 处理 Map
234
243
  if (source instanceof Map) {
235
244
  const copy = new Map();
236
245
  map.set(source, copy);
@@ -239,7 +248,7 @@ function cloneDeep(source, map = new WeakMap()) {
239
248
  });
240
249
  return copy;
241
250
  }
242
- // 处理 Set 类型
251
+ // 处理 Set
243
252
  if (source instanceof Set) {
244
253
  const copy = new Set();
245
254
  map.set(source, copy);
@@ -248,19 +257,47 @@ function cloneDeep(source, map = new WeakMap()) {
248
257
  });
249
258
  return copy;
250
259
  }
251
- // 处理 ArrayBuffer 类型
252
- if (source instanceof ArrayBuffer) {
253
- const copy = new ArrayBuffer(source.byteLength);
254
- new Uint8Array(copy).set(new Uint8Array(source));
260
+ // 处理数组 (包含稀疏数组)
261
+ if (Array.isArray(source)) {
262
+ const copy = Array.from({ length: source.length });
255
263
  map.set(source, copy);
264
+ // 克隆所有有效索引
265
+ for (let i = 0; i < source.length; i++) {
266
+ if (i in source) {
267
+ // 保留空位
268
+ copy[i] = cloneDeep(source[i], map);
269
+ }
270
+ }
271
+ // 克隆数组的自定义属性
272
+ const descriptors = Object.getOwnPropertyDescriptors(source);
273
+ for (const key of Reflect.ownKeys(descriptors)) {
274
+ Object.defineProperty(copy, key, {
275
+ ...descriptors[key],
276
+ value: cloneDeep(descriptors[key].value, map)
277
+ });
278
+ }
256
279
  return copy;
257
280
  }
258
- // 处理普通对象(包括原型链继承)
281
+ // 处理普通对象和类实例
259
282
  const copy = Object.create(Object.getPrototypeOf(source));
260
283
  map.set(source, copy);
261
- for (const key of Reflect.ownKeys(source)) {
262
- const value = source[key];
263
- copy[key] = cloneDeep(value, map);
284
+ const descriptors = Object.getOwnPropertyDescriptors(source);
285
+ for (const key of Reflect.ownKeys(descriptors)) {
286
+ const descriptor = descriptors[key];
287
+ if ('value' in descriptor) {
288
+ // 克隆数据属性
289
+ descriptor.value = cloneDeep(descriptor.value, map);
290
+ }
291
+ else {
292
+ // 处理访问器属性 (getter/setter)
293
+ if (descriptor.get) {
294
+ descriptor.get = cloneDeep(descriptor.get, map);
295
+ }
296
+ if (descriptor.set) {
297
+ descriptor.set = cloneDeep(descriptor.set, map);
298
+ }
299
+ }
300
+ Object.defineProperty(copy, key, descriptor);
264
301
  }
265
302
  return copy;
266
303
  }
package/lib/es/path.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/qs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/random.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/string.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/tooltip.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/tree.js CHANGED
@@ -1,16 +1,18 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
6
6
 
7
7
  import { objectOmit } from './object.js';
8
+ import { objectHas, isEmpty } from './type.js';
8
9
 
9
10
  const defaultFieldOptions = { keyField: 'key', childField: 'children', pidField: 'pid' };
10
11
  const defaultSearchTreeOptions = {
11
12
  childField: 'children',
12
13
  nameField: 'name',
13
- ignoreEmptyChild: false
14
+ removeEmptyChild: false,
15
+ ignoreCase: true
14
16
  };
15
17
  /**
16
18
  * 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
@@ -67,7 +69,9 @@ function forEachDeep(tree, iterator, children = 'children', isReverse = false) {
67
69
  walk(tree, null);
68
70
  }
69
71
  /**
70
- * 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
72
+ * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
73
+ *
74
+ * 可遍历任何带有 length 属性和数字键的类数组对象
71
75
  * @param {ArrayLike<V>} tree 树形数据
72
76
  * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
73
77
  * @param {string} children 定制子元素的key
@@ -303,20 +307,36 @@ function flatTree(treeList, options = defaultFieldOptions) {
303
307
  }
304
308
  /**
305
309
  * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
306
- * @param {any[]} nodes
307
- * @param {string} query
310
+ * 以下搜索条件二选一,按先后优先级处理:
311
+ * 1. 过滤函数filter, 返回true/false
312
+ * 2. 匹配关键词,支持是否启用忽略大小写来判断
313
+ *
314
+ * 有以下特性:
315
+ * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
316
+ * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
317
+ * @param {V[]} nodes
318
+ * @param {IFilterCondition} filterCondition
308
319
  * @param {ISearchTreeOpts} options
309
- * @returns {any[]}
320
+ * @returns {V[]}
310
321
  */
311
- function fuzzySearchTree(nodes, query, options = defaultSearchTreeOptions) {
322
+ function fuzzySearchTree(nodes, filterCondition, options = defaultSearchTreeOptions) {
323
+ if (!objectHas(filterCondition, 'filter') &&
324
+ (!objectHas(filterCondition, 'keyword') || isEmpty(filterCondition.keyword))) {
325
+ return nodes;
326
+ }
312
327
  const result = [];
313
328
  for (const node of nodes) {
314
329
  // 递归检查子节点是否匹配
315
330
  const matchedChildren = node[options.childField] && node[options.childField].length > 0
316
- ? fuzzySearchTree(node[options.childField] || [], query, options)
331
+ ? fuzzySearchTree(node[options.childField] || [], filterCondition, options)
317
332
  : [];
318
333
  // 检查当前节点是否匹配或者有匹配的子节点
319
- if (node[options.nameField].toLowerCase().includes(query.toLowerCase()) || matchedChildren.length > 0) {
334
+ if ((objectHas(filterCondition, 'filter')
335
+ ? filterCondition.filter(node)
336
+ : !options.ignoreCase
337
+ ? node[options.nameField].includes(filterCondition.keyword)
338
+ : node[options.nameField].toLowerCase().includes(filterCondition.keyword.toLowerCase())) ||
339
+ matchedChildren.length > 0) {
320
340
  // 将当前节点加入结果中
321
341
  if (node[options.childField]) {
322
342
  if (matchedChildren.length > 0) {
@@ -325,7 +345,7 @@ function fuzzySearchTree(nodes, query, options = defaultSearchTreeOptions) {
325
345
  [options.childField]: matchedChildren // 包含匹配的子节点
326
346
  });
327
347
  }
328
- else if (options.ignoreEmptyChild) {
348
+ else if (options.removeEmptyChild) {
329
349
  node[options.childField] && delete node[options.childField];
330
350
  result.push({
331
351
  ...node
package/lib/es/type.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/unique.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/es/url.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/lib/index.d.ts CHANGED
@@ -380,7 +380,7 @@ declare function objectMap<O extends AnyObject, T>(obj: O, iterator: (val: O[key
380
380
  */
381
381
  declare function objectPick<O extends AnyObject, K extends Extract<keyof O, string>[]>(obj: O, keys: K): Pick<O, ArrayElements<K>>;
382
382
  /**
383
- * 对象祛除
383
+ * 对象去除
384
384
  * @param {O} obj
385
385
  * @param {K} keys
386
386
  * @returns {Pick<O, ArrayElements<K>>}
@@ -410,6 +410,10 @@ declare function objectGet(obj: AnyObject, path: string, strict?: boolean): {
410
410
  };
411
411
  /**
412
412
  * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
413
+ *
414
+ * 包含对null、原始值、对象循环引用的处理
415
+ *
416
+ * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
413
417
  * @param {T} source
414
418
  * @param {WeakMap} map
415
419
  * @returns {T}
@@ -762,7 +766,12 @@ interface IFieldOptions {
762
766
  interface ISearchTreeOpts {
763
767
  childField: string;
764
768
  nameField: string;
765
- ignoreEmptyChild: boolean;
769
+ removeEmptyChild: boolean;
770
+ ignoreCase: boolean;
771
+ }
772
+ interface IFilterCondition<V> {
773
+ keyword?: string;
774
+ filter?: (args: V) => boolean;
766
775
  }
767
776
  /**
768
777
  * 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
@@ -774,14 +783,18 @@ interface ISearchTreeOpts {
774
783
  */
775
784
  declare function forEachDeep<V>(tree: ArrayLike<V>, iterator: (val: V, i: number, currentArr: ArrayLike<V>, tree: ArrayLike<V>, parent: V | null, level: number) => boolean | void, children?: string, isReverse?: boolean): void;
776
785
  /**
777
- * 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
786
+ * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
787
+ *
788
+ * 可遍历任何带有 length 属性和数字键的类数组对象
778
789
  * @param {ArrayLike<V>} tree 树形数据
779
790
  * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
780
791
  * @param {string} children 定制子元素的key
781
792
  * @param {boolean} isReverse 是否反向遍历
782
793
  * @returns {any[]} 新的一棵树
783
794
  */
784
- declare function mapDeep<V>(tree: ArrayLike<V>, iterator: (val: V, i: number, currentArr: ArrayLike<V>, tree: ArrayLike<V>, parent: V | null, level: number) => boolean | any, children?: string, isReverse?: boolean): any[];
795
+ declare function mapDeep<T>(tree: T[], iterator: (val: T, i: number, currentArr: T[], tree: T[], parent: T | null, level: number) => {
796
+ [k: string | number]: any;
797
+ } | boolean, children?: string, isReverse?: boolean): any[];
785
798
  type IdLike = number | string;
786
799
  interface ITreeConf {
787
800
  id: string | number;
@@ -851,12 +864,19 @@ declare function formatTree(list: any[], options?: IFieldOptions): any[];
851
864
  declare function flatTree(treeList: any[], options?: IFieldOptions): any[];
852
865
  /**
853
866
  * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
854
- * @param {any[]} nodes
855
- * @param {string} query
867
+ * 以下搜索条件二选一,按先后优先级处理:
868
+ * 1. 过滤函数filter, 返回true/false
869
+ * 2. 匹配关键词,支持是否启用忽略大小写来判断
870
+ *
871
+ * 有以下特性:
872
+ * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
873
+ * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
874
+ * @param {V[]} nodes
875
+ * @param {IFilterCondition} filterCondition
856
876
  * @param {ISearchTreeOpts} options
857
- * @returns {any[]}
877
+ * @returns {V[]}
858
878
  */
859
- declare function fuzzySearchTree(nodes: any[], query: string, options?: ISearchTreeOpts): any[];
879
+ declare function fuzzySearchTree<V>(nodes: V[], filterCondition: IFilterCondition<V>, options?: ISearchTreeOpts): V[];
860
880
 
861
881
  /**
862
882
  * 数值安全乘法
@@ -1057,4 +1077,4 @@ declare function replaceVarFromString(sourceStr: string, targetObj: Record<strin
1057
1077
  */
1058
1078
  declare function executeInScope(code: string, scope?: Record<string, any>): any;
1059
1079
 
1060
- export { type AnyArray, type AnyFunc, type AnyObject, type ArrayElements, type DateObj, type DateValue, type DebounceFunc, EMAIL_REGEX, type FileType, HEX_POOL, HTTP_URL_REGEX, type ICanvasWM, type ICompressOptions, type IFieldOptions, IPV4_REGEX, IPV6_REGEX, type ISearchTreeOpts, type ITreeConf, type IdLike, type LooseParamValue, type LooseParams, type ObjectAssignItem, type OnceFunc, PHONE_REGEX, type Params, type PartialDeep, type RandomString, type ReadyCallback, type Replacer, STRING_ARABIC_NUMERALS, STRING_LOWERCASE_ALPHA, STRING_POOL, STRING_UPPERCASE_ALPHA, type SetStyle, type SmoothScrollOptions, type Style, type ThrottleFunc, UNIQUE_NUMBER_SAFE_LENGTH, URL_REGEX, type UniqueString, type Url, type WithChildren, add, addClass, arrayEach, arrayEachAsync, arrayInsertBefore, arrayLike, arrayRemove, asyncMap, buildTree, calculateDate, calculateDateTime, chooseLocalFile, cloneDeep, compressImg, cookieDel, cookieGet, cookieSet, copyText, crossOriginDownload, dateParse, dateToEnd, dateToStart, debounce, decodeFromBase64, divide, downloadBlob, downloadData, downloadHref, downloadURL, encodeToBase64, escapeRegExp, executeInScope, flatTree, forEachDeep, formatDate, formatNumber, formatTree, fuzzySearchTree, genCanvasWM, getComputedCssVal, getGlobal, getStrWidthPx, getStyle, hasClass, isArray, isBigInt, isBoolean, isDate, isDigit, isDomReady, isEmail, isEmpty, isError, isFloat, isFunction, isIdNo, isInteger, isIpV4, isIpV6, isJsonString, isNaN, isNull, isNullOrUnDef, isNumber, isNumerical, isObject, isPhone, isPlainObject, isPrimitive, isRegExp, isString, isSymbol, isUndefined, isUrl, isValidDate, mapDeep, multiply, numberAbbr, numberToHex, objectAssign, objectEach, objectEachAsync, objectFill, objectGet, objectHas, objectMap, objectAssign as objectMerge, objectOmit, objectPick, onDomReady, once, parseQueryParams, parseVarFromString, pathJoin, pathNormalize, qsParse, qsStringify, randomNumber, randomString, randomUuid, removeClass, replaceVarFromString, searchTreeById, setGlobal, setStyle, smoothScroll, stringAssign, stringCamelCase, stringEscapeHtml, stringFill, stringFormat, stringKebabCase, strip, subtract, supportCanvas, throttle, tooltipEvent, typeIs, uniqueNumber, uniqueString, uniqueSymbol, urlDelParams, urlParse, urlSetParams, urlStringify, wait, weAtob, weBtoa };
1080
+ export { type AnyArray, type AnyFunc, type AnyObject, type ArrayElements, type DateObj, type DateValue, type DebounceFunc, EMAIL_REGEX, type FileType, HEX_POOL, HTTP_URL_REGEX, type ICanvasWM, type ICompressOptions, type IFieldOptions, type IFilterCondition, IPV4_REGEX, IPV6_REGEX, type ISearchTreeOpts, type ITreeConf, type IdLike, type LooseParamValue, type LooseParams, type ObjectAssignItem, type OnceFunc, PHONE_REGEX, type Params, type PartialDeep, type RandomString, type ReadyCallback, type Replacer, STRING_ARABIC_NUMERALS, STRING_LOWERCASE_ALPHA, STRING_POOL, STRING_UPPERCASE_ALPHA, type SetStyle, type SmoothScrollOptions, type Style, type ThrottleFunc, UNIQUE_NUMBER_SAFE_LENGTH, URL_REGEX, type UniqueString, type Url, type WithChildren, add, addClass, arrayEach, arrayEachAsync, arrayInsertBefore, arrayLike, arrayRemove, asyncMap, buildTree, calculateDate, calculateDateTime, chooseLocalFile, cloneDeep, compressImg, cookieDel, cookieGet, cookieSet, copyText, crossOriginDownload, dateParse, dateToEnd, dateToStart, debounce, decodeFromBase64, divide, downloadBlob, downloadData, downloadHref, downloadURL, encodeToBase64, escapeRegExp, executeInScope, flatTree, forEachDeep, formatDate, formatNumber, formatTree, fuzzySearchTree, genCanvasWM, getComputedCssVal, getGlobal, getStrWidthPx, getStyle, hasClass, isArray, isBigInt, isBoolean, isDate, isDigit, isDomReady, isEmail, isEmpty, isError, isFloat, isFunction, isIdNo, isInteger, isIpV4, isIpV6, isJsonString, isNaN, isNull, isNullOrUnDef, isNumber, isNumerical, isObject, isPhone, isPlainObject, isPrimitive, isRegExp, isString, isSymbol, isUndefined, isUrl, isValidDate, mapDeep, multiply, numberAbbr, numberToHex, objectAssign, objectEach, objectEachAsync, objectFill, objectGet, objectHas, objectMap, objectAssign as objectMerge, objectOmit, objectPick, onDomReady, once, parseQueryParams, parseVarFromString, pathJoin, pathNormalize, qsParse, qsStringify, randomNumber, randomString, randomUuid, removeClass, replaceVarFromString, searchTreeById, setGlobal, setStyle, smoothScroll, stringAssign, stringCamelCase, stringEscapeHtml, stringFill, stringFormat, stringKebabCase, strip, subtract, supportCanvas, throttle, tooltipEvent, typeIs, uniqueNumber, uniqueString, uniqueSymbol, urlDelParams, urlParse, urlSetParams, urlStringify, wait, weAtob, weBtoa };
package/lib/umd/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.7.0
2
+ * sculp-js v1.7.2
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -309,7 +309,7 @@
309
309
  return obj2;
310
310
  }
311
311
  /**
312
- * 对象祛除
312
+ * 对象去除
313
313
  * @param {O} obj
314
314
  * @param {K} keys
315
315
  * @returns {Pick<O, ArrayElements<K>>}
@@ -424,12 +424,16 @@
424
424
  }
425
425
  /**
426
426
  * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
427
+ *
428
+ * 包含对null、原始值、对象循环引用的处理
429
+ *
430
+ * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
427
431
  * @param {T} source
428
432
  * @param {WeakMap} map
429
433
  * @returns {T}
430
434
  */
431
435
  function cloneDeep(source, map = new WeakMap()) {
432
- // 处理原始类型(非对象/数组)和 null/undefined
436
+ // 处理原始类型和 null/undefined
433
437
  if (source === null || typeof source !== 'object') {
434
438
  return source;
435
439
  }
@@ -437,28 +441,33 @@
437
441
  if (map.has(source)) {
438
442
  return map.get(source);
439
443
  }
440
- // 处理 Date 类型
444
+ // 处理 ArrayBuffer
445
+ if (source instanceof ArrayBuffer) {
446
+ const copy = new ArrayBuffer(source.byteLength);
447
+ new Uint8Array(copy).set(new Uint8Array(source));
448
+ map.set(source, copy);
449
+ return copy;
450
+ }
451
+ // 处理 DataView 和 TypedArray (Uint8Array 等)
452
+ if (ArrayBuffer.isView(source)) {
453
+ const constructor = source.constructor;
454
+ const bufferCopy = cloneDeep(source.buffer, map);
455
+ return new constructor(bufferCopy, source.byteOffset, source.length);
456
+ }
457
+ // 处理 Date 对象
441
458
  if (source instanceof Date) {
442
459
  const copy = new Date(source.getTime());
443
460
  map.set(source, copy);
444
461
  return copy;
445
462
  }
446
- // 处理 RegExp 类型
463
+ // 处理 RegExp 对象
447
464
  if (source instanceof RegExp) {
448
465
  const copy = new RegExp(source.source, source.flags);
466
+ copy.lastIndex = source.lastIndex; // 保留匹配状态
449
467
  map.set(source, copy);
450
468
  return copy;
451
469
  }
452
- // 处理数组类型
453
- if (Array.isArray(source)) {
454
- const copy = [];
455
- map.set(source, copy);
456
- for (const item of source) {
457
- copy.push(cloneDeep(item, map));
458
- }
459
- return copy;
460
- }
461
- // 处理 Map 类型
470
+ // 处理 Map
462
471
  if (source instanceof Map) {
463
472
  const copy = new Map();
464
473
  map.set(source, copy);
@@ -467,7 +476,7 @@
467
476
  });
468
477
  return copy;
469
478
  }
470
- // 处理 Set 类型
479
+ // 处理 Set
471
480
  if (source instanceof Set) {
472
481
  const copy = new Set();
473
482
  map.set(source, copy);
@@ -476,19 +485,47 @@
476
485
  });
477
486
  return copy;
478
487
  }
479
- // 处理 ArrayBuffer 类型
480
- if (source instanceof ArrayBuffer) {
481
- const copy = new ArrayBuffer(source.byteLength);
482
- new Uint8Array(copy).set(new Uint8Array(source));
488
+ // 处理数组 (包含稀疏数组)
489
+ if (Array.isArray(source)) {
490
+ const copy = Array.from({ length: source.length });
483
491
  map.set(source, copy);
492
+ // 克隆所有有效索引
493
+ for (let i = 0; i < source.length; i++) {
494
+ if (i in source) {
495
+ // 保留空位
496
+ copy[i] = cloneDeep(source[i], map);
497
+ }
498
+ }
499
+ // 克隆数组的自定义属性
500
+ const descriptors = Object.getOwnPropertyDescriptors(source);
501
+ for (const key of Reflect.ownKeys(descriptors)) {
502
+ Object.defineProperty(copy, key, {
503
+ ...descriptors[key],
504
+ value: cloneDeep(descriptors[key].value, map)
505
+ });
506
+ }
484
507
  return copy;
485
508
  }
486
- // 处理普通对象(包括原型链继承)
509
+ // 处理普通对象和类实例
487
510
  const copy = Object.create(Object.getPrototypeOf(source));
488
511
  map.set(source, copy);
489
- for (const key of Reflect.ownKeys(source)) {
490
- const value = source[key];
491
- copy[key] = cloneDeep(value, map);
512
+ const descriptors = Object.getOwnPropertyDescriptors(source);
513
+ for (const key of Reflect.ownKeys(descriptors)) {
514
+ const descriptor = descriptors[key];
515
+ if ('value' in descriptor) {
516
+ // 克隆数据属性
517
+ descriptor.value = cloneDeep(descriptor.value, map);
518
+ }
519
+ else {
520
+ // 处理访问器属性 (getter/setter)
521
+ if (descriptor.get) {
522
+ descriptor.get = cloneDeep(descriptor.get, map);
523
+ }
524
+ if (descriptor.set) {
525
+ descriptor.set = cloneDeep(descriptor.set, map);
526
+ }
527
+ }
528
+ Object.defineProperty(copy, key, descriptor);
492
529
  }
493
530
  return copy;
494
531
  }
@@ -2179,7 +2216,8 @@
2179
2216
  const defaultSearchTreeOptions = {
2180
2217
  childField: 'children',
2181
2218
  nameField: 'name',
2182
- ignoreEmptyChild: false
2219
+ removeEmptyChild: false,
2220
+ ignoreCase: true
2183
2221
  };
2184
2222
  /**
2185
2223
  * 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
@@ -2236,7 +2274,9 @@
2236
2274
  walk(tree, null);
2237
2275
  }
2238
2276
  /**
2239
- * 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
2277
+ * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
2278
+ *
2279
+ * 可遍历任何带有 length 属性和数字键的类数组对象
2240
2280
  * @param {ArrayLike<V>} tree 树形数据
2241
2281
  * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
2242
2282
  * @param {string} children 定制子元素的key
@@ -2472,20 +2512,36 @@
2472
2512
  }
2473
2513
  /**
2474
2514
  * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
2475
- * @param {any[]} nodes
2476
- * @param {string} query
2515
+ * 以下搜索条件二选一,按先后优先级处理:
2516
+ * 1. 过滤函数filter, 返回true/false
2517
+ * 2. 匹配关键词,支持是否启用忽略大小写来判断
2518
+ *
2519
+ * 有以下特性:
2520
+ * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
2521
+ * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
2522
+ * @param {V[]} nodes
2523
+ * @param {IFilterCondition} filterCondition
2477
2524
  * @param {ISearchTreeOpts} options
2478
- * @returns {any[]}
2525
+ * @returns {V[]}
2479
2526
  */
2480
- function fuzzySearchTree(nodes, query, options = defaultSearchTreeOptions) {
2527
+ function fuzzySearchTree(nodes, filterCondition, options = defaultSearchTreeOptions) {
2528
+ if (!objectHas(filterCondition, 'filter') &&
2529
+ (!objectHas(filterCondition, 'keyword') || isEmpty(filterCondition.keyword))) {
2530
+ return nodes;
2531
+ }
2481
2532
  const result = [];
2482
2533
  for (const node of nodes) {
2483
2534
  // 递归检查子节点是否匹配
2484
2535
  const matchedChildren = node[options.childField] && node[options.childField].length > 0
2485
- ? fuzzySearchTree(node[options.childField] || [], query, options)
2536
+ ? fuzzySearchTree(node[options.childField] || [], filterCondition, options)
2486
2537
  : [];
2487
2538
  // 检查当前节点是否匹配或者有匹配的子节点
2488
- if (node[options.nameField].toLowerCase().includes(query.toLowerCase()) || matchedChildren.length > 0) {
2539
+ if ((objectHas(filterCondition, 'filter')
2540
+ ? filterCondition.filter(node)
2541
+ : !options.ignoreCase
2542
+ ? node[options.nameField].includes(filterCondition.keyword)
2543
+ : node[options.nameField].toLowerCase().includes(filterCondition.keyword.toLowerCase())) ||
2544
+ matchedChildren.length > 0) {
2489
2545
  // 将当前节点加入结果中
2490
2546
  if (node[options.childField]) {
2491
2547
  if (matchedChildren.length > 0) {
@@ -2494,7 +2550,7 @@
2494
2550
  [options.childField]: matchedChildren // 包含匹配的子节点
2495
2551
  });
2496
2552
  }
2497
- else if (options.ignoreEmptyChild) {
2553
+ else if (options.removeEmptyChild) {
2498
2554
  node[options.childField] && delete node[options.childField];
2499
2555
  result.push({
2500
2556
  ...node
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sculp-js",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "packageManager": "npm@8.19.2",
5
5
  "description": "js工具库",
6
6
  "scripts": {