@codady/utils 0.0.10 → 0.0.11

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.
@@ -0,0 +1,749 @@
1
+
2
+ /*!
3
+ * @since Last modified: 2025-12-24 17:11:26
4
+ * @name Utils for web front-end.
5
+ * @version 0.0.10
6
+ * @author AXUI development team <3217728223@qq.com>
7
+ * @description This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.
8
+ * @see {@link https://www.axui.cn|Official website}
9
+ * @see {@link https://github.com/codady/utils/issues|github issues}
10
+ * @see {@link https://gitee.com/codady/utils/issues|Gitee issues}
11
+ * @see {@link https://www.npmjs.com/package/@codady/utils|NPM}
12
+ * @issue QQ Group No.1:952502085
13
+ * @copyright This software supports the MIT License, allowing free learning and commercial use, but please retain the terms 'ax,' 'axui,' 'AX,' and 'AXUI' within the software.
14
+ * @license MIT license
15
+ */
16
+
17
+ (function (global, factory) {
18
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('assert')) :
19
+ typeof define === 'function' && define.amd ? define(['assert'], factory) :
20
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.utils = factory(global.assert));
21
+ })(this, (function (assert) { 'use strict';
22
+
23
+ const getDataType = (obj) => {
24
+ let tmp = Object.prototype.toString.call(obj).slice(8, -1), result;
25
+ if (tmp === 'Function' && /^\s*class\s+/.test(obj.toString())) {
26
+ result = 'Class';
27
+ }
28
+ else if (tmp === 'Object' && Object.getPrototypeOf(obj) !== Object.prototype) {
29
+ result = 'Instance';
30
+ }
31
+ else {
32
+ result = tmp;
33
+ }
34
+ return result;
35
+ //document.createElement -> HTMLxxxElement
36
+ //document.createDocumentFragment() -> DocumentFragment
37
+ //document.createComment() -> Comment
38
+ //document.createTextNode -> Text
39
+ //document.createCDATASection() -> XMLDocument
40
+ //document.createProcessingInstruction() -> ProcessingInstruction
41
+ //document.createRange() -> Range
42
+ //document.createTreeWalker() -> TreeWalker
43
+ //document.createNodeIterator() -> NodeIterator
44
+ //document.createElementNS('http://www.w3.org/2000/svg', 'svg'); -> SVGSVGElement
45
+ //document.createElementNS('http://www.w3.org/1998/Math/MathML', 'math'); -> MathMLElement
46
+ };
47
+
48
+ //支持原始值的复制,包括Number、String、Boolean、Null
49
+ //支持Date和Regex对象值复制(值转换)
50
+ //支持{},[],Set和Map的遍历
51
+ const deepClone = (data, options = {}) => {
52
+ const dataType = getDataType(data),
53
+ // Default options
54
+ opts = Object.assign({
55
+ cloneSet: true,
56
+ cloneMap: true,
57
+ cloneObject: true,
58
+ cloneArray: true,
59
+ //可重新构建
60
+ cloneDate: true,
61
+ cloneRegex: true,
62
+ //节点默认不复制
63
+ //cloneElement: false,
64
+ //cloneFragment: false,
65
+ }, options);
66
+ // Check interceptor - if it returns a value (not null/undefined), use it directly
67
+ if (opts.interceptor && typeof opts.interceptor === 'function') {
68
+ let result = opts.interceptor(data, dataType);
69
+ if ((result ?? false)) {
70
+ // Call onAfterClone if set
71
+ opts.onAfterClone?.({
72
+ output: result,
73
+ input: data,
74
+ type: dataType,
75
+ cloned: result !== data,
76
+ });
77
+ return result;
78
+ }
79
+ // If interceptor returns null/undefined, continue with normal cloning process
80
+ }
81
+ // Callback before cloning
82
+ opts.onBeforeClone?.(data, dataType);
83
+ let newData, cloned = true;
84
+ if (dataType === 'Object' && opts.cloneObject) {
85
+ const newObj = {}, symbols = Object.getOwnPropertySymbols(data);
86
+ // Clone regular properties
87
+ for (const key in data) {
88
+ newObj[key] = deepClone(data[key], opts);
89
+ }
90
+ // Clone Symbol properties
91
+ if (symbols.length > 0) {
92
+ for (const symbol of symbols) {
93
+ newObj[symbol] = deepClone(data[symbol], opts);
94
+ }
95
+ }
96
+ newData = newObj;
97
+ }
98
+ else if (dataType === 'Array' && opts.cloneArray) {
99
+ newData = data.map(item => deepClone(item, opts));
100
+ }
101
+ else if (dataType === 'Map' && opts.cloneMap) {
102
+ const newMap = new Map();
103
+ for (const [key, value] of data) {
104
+ // Both Map keys and values need deep cloning
105
+ newMap.set(deepClone(key, opts), deepClone(value, opts));
106
+ }
107
+ newData = newMap;
108
+ }
109
+ else if (dataType === 'Set' && opts.cloneSet) {
110
+ const newSet = new Set();
111
+ for (const value of data) {
112
+ // Set values need deep cloning
113
+ newSet.add(deepClone(value, opts));
114
+ }
115
+ newData = newSet;
116
+ }
117
+ else if (dataType === 'Date' && opts.cloneDate) {
118
+ newData = new Date(data.getTime());
119
+ }
120
+ else if (dataType === 'RegExp' && opts.cloneRegex) {
121
+ const regex = data;
122
+ newData = new RegExp(regex.source, regex.flags);
123
+ // } else if ((dataType.includes('HTML') && opts.cloneElement) ||
124
+ // (dataType === 'DocumentFragment' && opts.cloneFragment)
125
+ //) {
126
+ //Text,Comment,HTML*Element,DocumentFragment,Attr
127
+ // newData = (data as any).cloneNode(true) as T;
128
+ }
129
+ else {
130
+ // Number, String, Boolean, Symbol, Function,Error,Promise,ArrayBuffer,Blob,File, return directly
131
+ newData = data;
132
+ cloned = false;
133
+ }
134
+ // Callback after cloning
135
+ opts.onAfterClone?.({
136
+ output: newData,
137
+ input: data,
138
+ type: dataType,
139
+ cloned,
140
+ });
141
+ return newData;
142
+ };
143
+
144
+ const deepCloneToJSON = (data) => {
145
+ const dataType = getDataType(data);
146
+ // Handle objects
147
+ if (dataType === 'Object') {
148
+ const newObj = {};
149
+ // Clone regular properties
150
+ for (const key in data) {
151
+ newObj[key] = deepCloneToJSON(data[key]);
152
+ }
153
+ for (const key in newObj) {
154
+ newObj[key] === undefined && Reflect.deleteProperty(newObj, key);
155
+ }
156
+ return newObj;
157
+ // Handle arrays
158
+ }
159
+ else if (dataType === 'Array') {
160
+ let tmp = data.map((item, index) => deepCloneToJSON(item)).filter(item => item !== undefined);
161
+ return tmp;
162
+ // Handle Date objects
163
+ }
164
+ else if (!['Number', 'String', 'Boolean', 'Null'].includes(dataType)) {
165
+ return undefined;
166
+ }
167
+ else {
168
+ return data;
169
+ }
170
+ };
171
+
172
+ const arrayMutableMethods = [
173
+ 'push', 'pop', 'shift', 'unshift', 'splice',
174
+ 'sort', 'reverse', 'copyWithin', 'fill'
175
+ ];
176
+
177
+ const wrapArrayMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList, props = {}, }) => {
178
+ // Validation: Ensure target is an array and method is allowed
179
+ if (!Array.isArray(target)) {
180
+ throw new TypeError("The 'target' parameter must be an array.");
181
+ }
182
+ //使用默认
183
+ if (!allowList || allowList?.length) {
184
+ allowList = arrayMutableMethods;
185
+ }
186
+ const methods = {};
187
+ for (let method of allowList) {
188
+ methods[method] = function (...args) {
189
+ const context = {}, len = target.length;
190
+ // Capture "Pre-mutation" context for Undo/Redo/Tracking purposes
191
+ switch (method) {
192
+ // 'push' and 'unshift' are add operations, in undo/redo,
193
+ // we can determine the original data structure from result.length and args.length
194
+ // but methods that involve deletion need to record the deleted items to ensure the patch can restore the data structure during undo
195
+ case 'push':
196
+ case 'unshift':
197
+ context.addedItems = [...args];
198
+ break;
199
+ case 'pop':
200
+ context.poppedItem = target[len - 1];
201
+ break;
202
+ case 'shift':
203
+ context.shiftedItem = target[0];
204
+ break;
205
+ case 'splice':
206
+ const [s, d] = args,
207
+ // Calculate actual start index (handling negative values)
208
+ start = s < 0 ? Math.max(len + s, 0) : Math.min(s, len),
209
+ // Handle deleteCount defaults
210
+ deleteCount = d === undefined ? len - start : d;
211
+ context.deletedItems = target.slice(start, start + deleteCount);
212
+ break;
213
+ case 'sort':
214
+ case 'reverse':
215
+ // These methods reorder the whole array; requires a full shallow copy
216
+ context.oldSnapshot = [...target];
217
+ break;
218
+ case 'fill':
219
+ case 'copyWithin':
220
+ const startIdx = args[1] || 0, endIdx = args[2] === undefined ? len : args[2];
221
+ // Overwritten values
222
+ context.oldItems = target.slice(startIdx, endIdx);
223
+ context.start = startIdx;
224
+ context.end = endIdx;
225
+ break;
226
+ }
227
+ // Execute the "onBeforeMutate" callback before mutation
228
+ onBeforeMutate?.(context);
229
+ // Execute the native array mutation
230
+ const value = Array.prototype[method].apply(target, args), patch = {
231
+ value,
232
+ key: method,
233
+ args,
234
+ context,
235
+ target,
236
+ ...props
237
+ };
238
+ // Construct and trigger the patch callback
239
+ onAfterMutate?.(patch);
240
+ return value;
241
+ };
242
+ }
243
+ return methods;
244
+ };
245
+
246
+ const requireTypes = (data, require, cb) => {
247
+ // Normalize the input types (convert to array if it's a single type)
248
+ let requiredTypes = Array.isArray(require) ? require : [require], dataType = getDataType(data), typeLower = dataType.toLowerCase(),
249
+ // Normalize the type names (to lower case)
250
+ normalizedTypes = requiredTypes.map((type) => type.toLowerCase()),
251
+ // Check if the type is an HTML element (more specific than just 'html')
252
+ normalizedDataType = typeLower.includes('html') ? 'element' : typeLower;
253
+ // If callback exists, handle error through callback
254
+ if (cb) {
255
+ try {
256
+ if (!normalizedTypes.includes(normalizedDataType)) {
257
+ throw new TypeError(`Expected data type(s): [${normalizedTypes.join(', ')}], but got: ${normalizedDataType}`);
258
+ }
259
+ }
260
+ catch (error) {
261
+ cb(error, dataType);
262
+ }
263
+ }
264
+ else {
265
+ // If no callback is provided, throw an error directly
266
+ if (!normalizedTypes.includes(normalizedDataType)) {
267
+ throw new TypeError(`Expected data type(s): [${normalizedTypes.join(', ')}], but got: ${normalizedDataType}`);
268
+ }
269
+ }
270
+ return dataType;
271
+ };
272
+
273
+ const getUniqueId = (options = {}) => {
274
+ const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
275
+ // Current timestamp in milliseconds (since Unix epoch)
276
+ // This provides the primary uniqueness guarantee
277
+ const timestamp = Date.now(),
278
+ // Generate a base-36 random string (0-9, a-z)
279
+ // Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
280
+ // substring(2, 11) extracts 9 characters starting from index 2
281
+ //0.259854635->0.9crs03e8v2
282
+ base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
283
+ // Additional 4-digit random number for extra randomness
284
+ // This helps avoid collisions in high-frequency generation scenarios
285
+ base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
286
+ // Construct the final ID string
287
+ // Format: [prefix_]timestamp_randomBase36_extraRandom
288
+ return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
289
+ };
290
+
291
+ const setMutableMethods = ['add', 'delete', 'clear'];
292
+
293
+ const mapMutableMethods = ['set', 'delete', 'clear'];
294
+
295
+ const wrapSetMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList = setMutableMethods, props = {}, }) => {
296
+ // Validation: Ensure target is a Set
297
+ if (!(target instanceof Set)) {
298
+ throw new TypeError("The 'target' parameter must be a Set.");
299
+ }
300
+ // Create method wrappers
301
+ const methods = {};
302
+ // Helper to create wrapped method
303
+ const createWrappedMethod = (method) => {
304
+ return function (...args) {
305
+ const context = {};
306
+ // Capture pre-mutation context based on method
307
+ switch (method) {
308
+ case 'add': {
309
+ const [value] = args;
310
+ context.addedItem = value;
311
+ //context.existed=true,说明值重复
312
+ context.existed = target.has(value);
313
+ break;
314
+ }
315
+ case 'delete': {
316
+ const [value] = args;
317
+ context.existed = target.has(value);
318
+ context.deletedItem = context.existed ? value : undefined;
319
+ break;
320
+ }
321
+ case 'clear': {
322
+ context.clearedItems = Array.from(target);
323
+ //用来做验证
324
+ context.previousSize = target.size;
325
+ break;
326
+ }
327
+ }
328
+ // Execute before mutation callback
329
+ onBeforeMutate(context);
330
+ // Execute the native Set method
331
+ const result = target[method].apply(target, args);
332
+ // Construct patch object
333
+ const patch = {
334
+ method,
335
+ result,
336
+ args,
337
+ context,
338
+ target,
339
+ ...props
340
+ };
341
+ // Execute after mutation callback
342
+ onAfterMutate(patch);
343
+ return result;
344
+ };
345
+ };
346
+ // Wrap allowed methods
347
+ for (const method of allowList) {
348
+ if (setMutableMethods.includes(method)) {
349
+ methods[method] = createWrappedMethod(method);
350
+ }
351
+ }
352
+ // Add target reference
353
+ Object.defineProperty(methods, 'target', {
354
+ get: () => target,
355
+ enumerable: false,
356
+ configurable: false
357
+ });
358
+ return methods;
359
+ };
360
+
361
+ const wrapMapMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList = mapMutableMethods, props = {}, }) => {
362
+ // Validation: Ensure target is a Map
363
+ if (!(target instanceof Map)) {
364
+ throw new TypeError("The 'target' parameter must be a Map.");
365
+ }
366
+ // Create method wrappers
367
+ const methods = {};
368
+ // Helper to create wrapped method
369
+ const createWrappedMethod = (method) => {
370
+ return function (...args) {
371
+ const context = {};
372
+ // Capture pre-mutation context based on method
373
+ switch (method) {
374
+ case 'set': {
375
+ const [key, newValue] = args;
376
+ context.key = key;
377
+ context.newValue = newValue;
378
+ context.existed = target.has(key);
379
+ context.oldValue = context.existed ? target.get(key) : undefined;
380
+ break;
381
+ }
382
+ case 'delete': {
383
+ const [key] = args;
384
+ context.key = key;
385
+ context.existed = target.has(key);
386
+ context.value = context.existed ? target.get(key) : undefined;
387
+ break;
388
+ }
389
+ case 'clear': {
390
+ context.clearedItems = Array.from(target.entries());
391
+ //用来做验证
392
+ context.previousSize = target.size;
393
+ break;
394
+ }
395
+ }
396
+ // Execute before mutation callback
397
+ onBeforeMutate(context);
398
+ // Execute the native Map method
399
+ const result = target[method].apply(target, args);
400
+ // Construct patch object
401
+ const patch = {
402
+ method,
403
+ result,
404
+ args,
405
+ context,
406
+ target,
407
+ ...props
408
+ };
409
+ // Execute after mutation callback
410
+ onAfterMutate(patch);
411
+ return result;
412
+ };
413
+ };
414
+ // Wrap allowed methods
415
+ for (const method of allowList) {
416
+ if (mapMutableMethods.includes(method)) {
417
+ methods[method] = createWrappedMethod(method);
418
+ }
419
+ }
420
+ // Add target reference
421
+ Object.defineProperty(methods, 'target', {
422
+ get: () => target,
423
+ enumerable: false,
424
+ configurable: false
425
+ });
426
+ return methods;
427
+ };
428
+
429
+ const deepEqual = (a, b) => {
430
+ // If both are equal by reference
431
+ if (a === b)
432
+ return true;
433
+ // If both are arrays, check equality recursively
434
+ if (Array.isArray(a) && Array.isArray(b)) {
435
+ if (a.length !== b.length)
436
+ return false;
437
+ for (let i = 0; i < a.length; i++) {
438
+ if (!deepEqual(a[i], b[i]))
439
+ return false;
440
+ }
441
+ return true;
442
+ }
443
+ // If both are objects, check equality recursively
444
+ if (typeof a === 'object' && typeof b === 'object') {
445
+ const keysA = Object.keys(a), keysB = Object.keys(b);
446
+ if (keysA.length !== keysB.length)
447
+ return false;
448
+ for (let key of keysA) {
449
+ if (!keysB.includes(key) || !deepEqual(a[key], b[key]))
450
+ return false;
451
+ }
452
+ return true;
453
+ }
454
+ // For other types, direct comparison
455
+ return a === b;
456
+ };
457
+
458
+ const deepMergeMaps = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
459
+ // Ensure both target and source are Maps
460
+ if (!(target instanceof Map) || !(source instanceof Map))
461
+ return target;
462
+ // Merge options, with default values
463
+ const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, options),
464
+ // If cloning is enabled, create a deep copy of the target Map
465
+ result = opts.targetClone ? new Map(target) : target;
466
+ // Handle different merge strategies based on itemMode
467
+ if (opts.itemMode === 'replace') {
468
+ // Replace mode: clear the target Map and add all entries from the source Map
469
+ result.clear();
470
+ source.forEach((value, key) => result.set(key, value));
471
+ return result;
472
+ }
473
+ else if (opts.itemMode === 'concat') {
474
+ // Concatenate mode: add all entries from the source Map to the target Map
475
+ source.forEach((value, key) => result.set(key, value));
476
+ return result;
477
+ }
478
+ else {
479
+ // Default "merge" mode: recursively merge entries in the Maps
480
+ source.forEach((value, key) => {
481
+ // Check if the key already exists in the target Map
482
+ if (result.has(key)) {
483
+ const targetValue = result.get(key), sourceValue = value;
484
+ const targetType = getDataType(targetValue), sourceType = getDataType(sourceValue);
485
+ // If both target and source values are of the same type, merge them
486
+ if (targetType === sourceType) {
487
+ if (targetType === 'Object') {
488
+ // If both target and source are objects, merge them recursively
489
+ result.set(key, deepMergeObjects(targetValue, sourceValue, opts));
490
+ }
491
+ else if (targetType === 'Array') {
492
+ // If both target and source are arrays, merge them recursively
493
+ deepMergeArrays(targetValue, sourceValue, opts);
494
+ }
495
+ else if (targetType === 'Map') {
496
+ // If both target and source are Maps, merge them recursively
497
+ deepMergeMaps(targetValue, sourceValue, opts);
498
+ }
499
+ else if (targetType === 'Set') {
500
+ // If both target and source are Sets, merge them recursively
501
+ deepMergeSets(targetValue, sourceValue, opts);
502
+ }
503
+ else {
504
+ // For simple values, overwrite the target value with the source value
505
+ result.set(key, sourceValue);
506
+ }
507
+ }
508
+ else {
509
+ // If types don't match, overwrite the target value with the source value
510
+ result.set(key, sourceValue);
511
+ }
512
+ }
513
+ else {
514
+ // If the key doesn't exist in the target, add the entry from the source Map
515
+ result.set(key, value);
516
+ }
517
+ });
518
+ return result;
519
+ }
520
+ };
521
+
522
+ const deepMergeSets = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
523
+ // Ensure both target and source are Sets
524
+ if (!(target instanceof Set) || !(source instanceof Set))
525
+ return target;
526
+ // Merge options, with default values
527
+ const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, options),
528
+ // If cloning is enabled, create a deep copy of the target Set
529
+ result = opts.targetClone ? new Set(target) : target;
530
+ // Handle different merge strategies based on itemMode
531
+ if (opts.itemMode === 'replace') {
532
+ // Replace mode: clear the target Set and add all items from the source Set
533
+ result.clear();
534
+ for (let item of source)
535
+ result.add(item);
536
+ return result;
537
+ }
538
+ else if (opts.itemMode === 'concat') {
539
+ // Concatenate mode: add all items from the source Set to the target Set
540
+ for (let item of source)
541
+ result.add(item);
542
+ return result;
543
+ }
544
+ else {
545
+ // Default "merge" mode: recursively merge items in the Sets
546
+ for (let item of source) {
547
+ // Check the type of the target and source items
548
+ const _target = [...result].find(val => deepEqual(val, item)), _targetType = getDataType(_target), // Find existing value in the target Set
549
+ _sourceType = getDataType(item);
550
+ // If both target and source are arrays, merge them recursively
551
+ if (_targetType === 'Array' && _sourceType === 'Array') {
552
+ deepMergeArrays(_target, item, opts);
553
+ // If both target and source are objects, merge them recursively
554
+ }
555
+ else if (_targetType === 'Object' && _sourceType === 'Object') {
556
+ deepMergeObjects(_target, item, opts);
557
+ // If both target and source are Maps, merge them recursively
558
+ }
559
+ else if (_targetType === 'Map' && _sourceType === 'Map') {
560
+ deepMergeMaps(_target, item, opts);
561
+ // If both target and source are Sets, merge them recursively
562
+ }
563
+ else if (_targetType === 'Set' && _sourceType === 'Set') {
564
+ deepMergeSets(_target, item, opts);
565
+ // If the items are simple values, add them directly
566
+ }
567
+ else {
568
+ result.add(item);
569
+ }
570
+ }
571
+ return result;
572
+ }
573
+ };
574
+
575
+ const deepMergeArrays = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
576
+ // Ensure both target and source are arrays
577
+ if (!Array.isArray(target) || !Array.isArray(source))
578
+ return target;
579
+ // Merge options, with default values
580
+ const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false }, options),
581
+ // If cloning is enabled, create a deep copy of the target array
582
+ result = opts.targetClone ? [...target] : target;
583
+ // Handle different merge strategies based on itemMode
584
+ if (opts.itemMode === 'replace') {
585
+ // Replace mode: clear the target array and push all items from the source array
586
+ result.length = 0;
587
+ result.push(...source);
588
+ return result;
589
+ }
590
+ else if (opts.itemMode === 'concat') {
591
+ // Concatenate mode: append all items from the source array to the target array
592
+ result.push(...source);
593
+ return result;
594
+ }
595
+ else {
596
+ // Default "merge" mode: recursively merge items in the arrays
597
+ for (let i = 0; i < source.length; i++) {
598
+ const _targetType = getDataType(result[i]), _sourceType = getDataType(source[i]);
599
+ // If both target and source are arrays, merge them recursively
600
+ if (_targetType === 'Array' && _sourceType === 'Array') {
601
+ deepMergeArrays(result[i], source[i], opts);
602
+ // If both target and source are objects, merge them recursively
603
+ }
604
+ else if (_targetType === 'Object' && _sourceType === 'Object') {
605
+ deepMergeObjects(result[i], source[i], opts);
606
+ // If both target and source are Maps, merge them recursively
607
+ }
608
+ else if (_targetType === 'Map' && _sourceType === 'Map') {
609
+ deepMergeMaps(result[i], source[i], opts);
610
+ // If both target and source are Sets, merge them recursively
611
+ }
612
+ else if (_targetType === 'Set' && _sourceType === 'Set') {
613
+ deepMergeSets(result[i], source[i], opts);
614
+ // If the items are simple values, replace them directly
615
+ }
616
+ else {
617
+ result[i] = source[i];
618
+ }
619
+ }
620
+ return result;
621
+ }
622
+ };
623
+
624
+ const deepMergeObjects = (target, source, opts = {}) => {
625
+ let targetType = getDataType(target), sourceType = getDataType(source);
626
+ //target不是对象或者source为空则直接返回
627
+ if (targetType !== 'Object' || sourceType !== 'Object') {
628
+ return target;
629
+ }
630
+ const options = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, opts),
631
+ //如果是复制方法,则先复制target
632
+ result = options.targetClone ? deepClone(target) : target;
633
+ for (let k in source) {
634
+ if (source.hasOwnProperty(k) && result.hasOwnProperty(k)) {
635
+ let _resultType = getDataType(result[k]), _sourceType = getDataType(source[k]);
636
+ if (_resultType !== _sourceType) {
637
+ //类型不同则直接覆盖
638
+ if (options.useEnable && result.hasOwnProperty(k) && result[k]?.hasOwnProperty('enable') && typeof source[k] === 'boolean') {
639
+ //部分替换,仅针对result={enable:true/false,a:''},source=false/true这种情况和相反的情况,因为这种情况再笨框架比较多见
640
+ if (result[k]?.hasOwnProperty('enable') && typeof source[k] === 'boolean') {
641
+ //result={enable:true,a:'',b:''},source[k]=false=>result={enable:false,a:'',b:''}
642
+ result[k].enable = source[k];
643
+ }
644
+ else if (source[k]?.hasOwnProperty('enable') && typeof result[k] === 'boolean') {
645
+ //source={enable:true,a:'',b:''},(result as any)[k]=false=>result={enable:false,a:'',b:''}
646
+ result = Object.assign({ enable: result[k] }, source[k]);
647
+ }
648
+ else {
649
+ //完全替换
650
+ result[k] = source[k];
651
+ }
652
+ }
653
+ else {
654
+ //完全替换
655
+ result[k] = source[k];
656
+ }
657
+ }
658
+ else {
659
+ //类型相同
660
+ if (_sourceType === 'Object') {
661
+ //如果遇上对象则深度复制
662
+ result[k] = deepMergeObjects(result[k], source[k], options);
663
+ }
664
+ else if (_sourceType === 'Array') {
665
+ //如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
666
+ deepMergeArrays(result[k], source[k], options);
667
+ }
668
+ else if (_sourceType === 'Map') {
669
+ //如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
670
+ deepMergeMaps(result[k], source[k], options);
671
+ }
672
+ else if (_sourceType === 'Set') {
673
+ //如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
674
+ deepMergeSets(result[k], source[k], options);
675
+ }
676
+ else {
677
+ //其他清空则直接覆盖
678
+ result[k] = source[k];
679
+ }
680
+ }
681
+ }
682
+ else if (source.hasOwnProperty(k) && !result.hasOwnProperty(k) && options.propAppend) {
683
+ //如果source有属性,result没有该属性,但是options允许追加属性则直接赋值
684
+ result[k] = source[k];
685
+ }
686
+ }
687
+ //Symbol键直接追加,因为Symbol是唯一,结果同Object.assign
688
+ if (options.useSymbol) {
689
+ let symbols = Object.getOwnPropertySymbols(source);
690
+ if (symbols.length > 0) {
691
+ for (let k of symbols) {
692
+ result[k] = source[k];
693
+ }
694
+ }
695
+ }
696
+ return result;
697
+ };
698
+
699
+ const deepMerge = (target, source, opts = {}) => {
700
+ // Get the data types of the target and source
701
+ let targetType = getDataType(target), sourceType = getDataType(source), options = Object.assign({
702
+ itemMode: 'merge', // Default merge mode
703
+ propAppend: true, // Default to appending properties from source to target
704
+ targetClone: false, // Do not clone target by default
705
+ useEnable: true // Enable special handling for objects with an `enable` property
706
+ }, opts);
707
+ if (targetType === 'Object' && sourceType === 'Object') {
708
+ return deepMergeObjects(target, source, options);
709
+ }
710
+ else if (targetType === 'Array' && sourceType === 'Array') {
711
+ return deepMergeArrays(target, source, options);
712
+ }
713
+ else if (targetType === 'Set' && sourceType === 'Set') {
714
+ return deepMergeSets(target, source, options);
715
+ }
716
+ else if (targetType === 'Map' && sourceType === 'Map') {
717
+ return deepMergeMaps(target, source, options);
718
+ }
719
+ else {
720
+ return target;
721
+ }
722
+ };
723
+
724
+ const utils = {
725
+ //executeStr,
726
+ getDataType,
727
+ requireTypes,
728
+ //renderTpl,
729
+ //parseStr,
730
+ deepClone,
731
+ deepCloneToJSON,
732
+ wrapArrayMethods,
733
+ arrayMutableMethods,
734
+ setMutableMethods,
735
+ mapMutableMethods,
736
+ wrapSetMethods,
737
+ wrapMapMethods,
738
+ getUniqueId,
739
+ deepEqual: assert.deepEqual,
740
+ deepMerge,
741
+ deepMergeArrays,
742
+ deepMergeMaps,
743
+ deepMergeObjects,
744
+ deepMergeSets,
745
+ };
746
+
747
+ return utils;
748
+
749
+ }));